网络编程之socket库函数.docx
《网络编程之socket库函数.docx》由会员分享,可在线阅读,更多相关《网络编程之socket库函数.docx(14页珍藏版)》请在冰豆网上搜索。
网络编程之socket库函数
一、典型过程图
1.1面向连接的套接字的系统调用时序图
1.2无连接协议的套接字调用时序图
1.3面向连接的应用程序流程图
#include//write()
#include//Sleep()
二、程序讲解
VC中使用:
#include
#pragmacomment(lib,"WS2_32.lib")
******************************服务器端*************************************
1、服务器进程创建套接字。
服务进程总是先于客户进程启动。
2、服务进程首先调用socket()函数创建自已端的一个字节流套接字,并提供三个参数:
网络地址类型,一般取AF_INEF(AdressfamilyInterNET);套接字类型,这里取SOCK_STREAM;网络协议,缺省为TCP/IP协议,对应参数为0。
sockfd=socket(AF_INET,SOCK_STREAM,0)
3、给套接字地址变量赋初值。
在生成套接字后,要用服务器的地址先对sockaddr_in结构变量赋初值。
sockaddr_in在/usr/include/netinet/in.h(或者Winsock2.h)中有定义,它只适用于INTERNET地址类型,含有INTERNET套接字地址类型、IP端口号、IP地址等信息。
structsockaddr_in{
shortsin_family;=AF_INET;/*网络类型使用ARPAinternet地址*/
u_shortsin_port;=htons(1234);/*设定IP端口号,并将主机字节顺序转换为网络字节顺序*/
structn_addrsin_addr;=htonl(INADDR_ANY);/*IP地址取公认的任意合法地址*/INADDR_ANY=(u_long)0x00000000
charin_zero[8];
};
地址类型可取定为AF_INET,IP地址对服务器可取任意合法地址INADDR_ANY。
IP端口号可由用户设定,但要注意主机字节顺序向网络字节顺序的转换。
4、给套接字命名。
由socket()函数创建的套接字是没有名字的。
所谓命名,就是用bind()函数将服务器地址捆绑到创建的套接字sockfd上。
bind(sockfd,(structsockaddr*)&serv_addr,sizeof(serv_addr))
//serv_addr,(structsockaddr_in)
5、服务器进程准备接受来自客户机的连接请求。
首先调用listen()函数,让服务器进程进入监听状态;
listen(sockfd,5)/*建立长度为5的监听队列从套接字上收听连接请求*/
然后调用accept()函数,准备接受客户机的连接信号。
clilen=sizeof(cli_addr);
newsockfd=accept(sockfd,(structsockaddr*)&cli_addr,&clilen);
无连接请求时,服务进程被阻塞。
******************************客户端*************************************
6、客户进程调用socket()函数创建已端的套接字。
sockfd=socket(AF_INET,SOCK_STREAM,0)
7、给客户端的sockaddr_in结构体变量赋值。
地址类型仍可取AF_INET,端口号和服务器方的端口号相同,
structsockaddr_in{
shortsin_family;=AF_INET;/*网络类型使用ARPAinternet地址*/
u_shortsin_port;=htons(1234);/*设定IP端口号,并将主机字节顺序转换为网络字节顺序*/
structn_addrsin_addr.s_addr=inet_addr(argv[1]);=/*服务器IP地址转换为长整型IP地址,并赋予地址结构变量*/
charin_zero[8];
};
欲连服务器的地址通过调用inet_addr()转换得到。
也可通过gethostbyname()函数将名字转换为指向hostent结构变量的指针,再将hostent结构变量的地址成员用memcpy()复制到sockaddr_in结构变量上。
8、客户方调用connect()函数向服务进程发出连接请求。
connect(sockfd,(structsockaddr*)&serv_addr,sizeof(serv_addr))
******************************服务器端*************************************
9、当连接请求到来后,被阻塞服务进程的accpet()函数生成一个新的字节流套接字,并返回客户机的sockaddr_in结构变量,
newsockfd=accept(sockfd,(structsockaddr*)&cli_addr,&clilen);
/*有连接请求时,返回被赋予客户机地址的新套接字,连接已建立*/
从而在服务器应用程序中用新的被赋予客户机地址的套接字同客户进程进行连接,然后向客户方返回接受信号。
10、一旦客户机的套接字收到来自服务器的接受信号,则表示客户机与服务器双方已实现连接。
任一方均可向对方发送,也可接收对方发来的数据。
这既可通过send()、recv()函数来实现。
也可通过read()、write()函数来交换数据。
11、服务进程和客户进程可通过调用shutdown()和colse()关闭套接字上的所有发送和接收操作,撤销套接字并中断连接。
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include"sys/types.h"
//#include"netinet/in.h"
//#include"sys/socket.h"
//#include
#include
#pragmacomment(lib,"ws2_32.lib")
//#include"sys/wait.h"
#include"sys/stat.h"
#include
#include"malloc.h"
#include"time.h"
与socket有关的一些函数介绍
1、读取当前错误值:
每次发生错误时,如果要对具体问题进行处理,那么就应该调用这个函数取得错误代码。
intWSAGetLastError(void);
#defineh_errnoWSAGetLastError()
错误值请自己阅读Winsock2.h。
2、将主机的unsignedlong值转换为网络字节顺序(32位):
为什么要这样做呢?
因为不同的计算机使用不同的字节顺序存储数据。
因此任何从Winsock函数对IP地址和端口号的引用和传给Winsock函数的IP地址和端口号均时按照网络顺序组织的。
u_longhtonl(u_longhostlong);
举例:
htonl(0)=0
htonl(80)=1342177280
3、将unsignedlong数从网络字节顺序转换位主机字节顺序,是上面函数的逆函数。
u_longntohl(u_longnetlong);
举例:
ntohl(0)=0
ntohl(1342177280)=80
4、将主机的unsignedshort值转换为网络字节顺序(16位):
原因同2:
u_shorthtons(u_shorthostshort);
举例:
htonl(0)=0
htonl(80)=20480
5、将unsignedshort数从网络字节顺序转换位主机字节顺序,是上面函数的逆函数。
u_shortntohs(u_shortnetshort);
举例:
ntohs(0)=0
ntohsl(20480)=80
6、将用点分割的IP地址转换位一个in_addr结构的地址,这个结构的定义见笔记
(一),实际上就是一个unsignedlong值。
计算机内部处理IP地址可是不认识如192.1.8.84之类的数据。
unsignedlonginet_addr(constcharFAR*cp);
举例:
inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")=16777343
如果发生错误,函数返回INADDR_NONE值。
7、将网络地址转换位用点分割的IP地址,是上面函数的逆函数。
charFAR*inet_ntoa(structin_addrin);
举例:
char*ipaddr=NULL;
charaddr[20];
in_addrinaddr;
inaddr.s_addr=16777343;
ipaddr=inet_ntoa(inaddr);
strcpy(addr,ipaddr);
这样addr的值就变为127.0.0.1。
注意意不要修改返回值或者进行释放动作。
如果函数失败就会返回NULL值。
8、获取套接字的本地地址结构:
intgetsockname(SOCKETs,structsockaddrFAR*name,intFAR*namelen);
s为套接字
name为函数调用后获得的地址值
namelen为缓冲区的大小。
9、获取与套接字相连的端地址结构:
intgetpeername(SOCKETs,structsockaddrFAR*name,intFAR*namelen);
s为套接字
name为函数调用后获得的端地址值
namelen为缓冲区的大小。
10、获取计算机名:
intgethostname(charFAR*name,intnamelen);
name是存放计算机名的缓冲区
namelen是缓冲区的大小
用法:
charszName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//错误处理
}
返回值为:
szNmae="xiaojin"
11、根据计算机名获取主机地址:
structhostentFAR*gethostbyname(constcharFAR*name);
name为计算机名。
用法:
hostent*host;
char*ip;
host=gethostbyname("xiaojin");
if(host->h_addr_list[0])
{
structin_addraddr;
memmove(&addr,host->h_addr_list[0],4);
//获得标准IP地址
ip=inet_ntoa(addr);
}
返回值为:
hostent->h_name="xiaojin"
hostent->h_addrtype=2//AF_INET
hostent->length=4
ip="127.0.0.1"
Winsock的I/O操作:
1、两种I/O模式
∙阻塞模式:
执行I/O操作完成前会一直进行等待,不会将控制权交给程序。
套接字默认为阻塞模式。
可以通过多线程技术进行处理。
∙非阻塞模式:
执行I/O操作时,Winsock函数会返回并交出控制权。
这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回WSAEWOULDBLOCK错误。
但功能强大。
为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种:
2、select模型:
通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或
者能否向一个套接字写入数据。
intselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,
fd_setFAR*exceptfds,conststructtimevalFAR*timeout);
◆先来看看涉及到的结构的定义:
a、d_set结构:
#defineFD_SETSIZE64?
typedefstructfd_set{
u_intfd_count;/*howmanyareSET?
*/
SOCKETfd_array[FD_SETSIZE];/*anarrayofSOCKETs*/
}fd_set;
fd_count为已设定socket的数量
fd_array为socket列表,FD_SETSIZE为最大socket数量,建议不小于64。
这是微软建
议的。
B、timeval结构:
structtimeval{
longtv_sec;/*seconds*/
longtv_usec;/*andmicroseconds*/
};
tv_sec为时间的秒值。
tv_usec为时间的毫秒值。
这个结构主要是设置select()函数的等待值,如果将该结构设置为(0,0),则select()函数
会立即返回。
◆再来看看select函数各参数的作用:
1nfds:
没有任何用处,主要用来进行系统兼容用,一般设置为0。
2readfds:
等待可读性检查的套接字组。
3writefds;等待可写性检查的套接字组。
4exceptfds:
等待错误检查的套接字组。
5timeout:
超时时间。
6函数失败的返回值:
调用失败返回SOCKET_ERROR,超时返回0。
readfds、writefds、exceptfds三个变量至少有一个不为空,同时这个不为空的套接字组
种至少有一个socket,道理很简单,否则要select干什么呢。
举例:
测试一个套接字是否可读:
fd_setfdread;
//FD_ZERO定义
//#defineFD_ZERO(set)(((fd_setFAR*)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread);//加入套接字,详细定义请看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread)//是否存在fread中,详细定义请看winsock2.h
{
//是可读的
}
}
◆I/O操作函数:
主要用于获取与套接字相关的操作参数。
intioctlsocket(SOCKETs,longcmd,u_longFAR*argp);
s为I/O操作的套接字。
cmd为对套接字的操作命令。
argp为命令所带参数的指针。
常见的命令:
//确定套接字自动读入的数据量
#defineFIONREAD_IOR(''''f'''',127,u_long)/*get#bytestoread*/
//允许或禁止套接字的非阻塞模式,允许为非0,禁止为0
#defineFIONBIO_IOW(''''f'''',126,u_long)/*set/clearnon-blockingi/o*/
//确定是否所有带外数据都已被读入
#defineSIOCATMARK_IOR(''''s'''',7,u_long)/*atoobmark?
*/
3、WSAAsynSelect模型:
WSAAsynSelect模型也是一个常用的异步I/O模型。
应用程序可以在一个套接字上接收以
WINDOWS消息为基础的网络事件通知。
该模型的实现方法是通过调用WSAAsynSelect函
数自动将套接字设置为非阻塞模式,并向WINDOWS注册一个或多个网络时间,并提供一
个通知时使用的窗口句柄。
当注册的事件发生时,对应的窗口将收到一个基于消息的通知。
intWSAAsyncSelect(SOCKETs,HWNDhWnd,u_intwMsg,longlEvent);
s为需要事件通知的套接字
hWnd为接收消息的窗口句柄
wMsg为要接收的消息
lEvent为掩码,指定应用程序感兴趣的网络事件组合,主要如下:
#defineFD_READ_BIT0
#defineFD_READ(1<#defineFD_WRITE_BIT1
#defineFD_WRITE(1<#defineFD_OOB_BIT2
#defineFD_OOB(1<#defineFD_ACCEPT_BIT3
#defineFD_ACCEPT(1<#defineFD_CONNECT_BIT4
#defineFD_CONNECT(1<#defineFD_CLOSE_BIT5
#defineFD_CLOSE(1<用法:
要接收读写通知:
intnResult=WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//错误处理
}
取消通知:
intnResult=WSAAsyncSelect(s,hWnd,0,0);
当应用程序窗口hWnd收到消息时,wMsg.wParam参数标识了套接字,lParam的低字标明
了网络事件,高字则包含错误代码。
4、WSAEventSelect模型
WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发
送到一个事件对象句柄,而不是发送到一个窗口。
使用步骤如下:
a、创建事件对象来接收网络事件:
#defineWSAEVENTHANDLE
#defineLPWSAEVENTLPHANDLE
WSAEVENTWSACreateEvent(void);
该函数的返回值为一个事件对象句柄,它具有两种工作状态:
已传信(signaled)和未传信
(nonsignaled)以及两种工作模式:
人工重设(manualreset)和自动重设(autoreset)。
默认未
未传信的工作状态和人工重设模式。
b、将事件对象与套接字关联,同时注册事件,使事件对象的工作状态从未传信转变未
已传信。
intWSAEventSelect(SOCKETs,WSAEVENThEventObject,longlNetworkEvents);
s为套接字
hEventObject为刚才创建的事件对象句柄
lNetworkEvents为掩码,定义如上面所述
c、I/O处理后,设置事件对象为未传信
BOOLWSAResetEvent(WSAEVENThEvent);
Hevent为事件对象
成功返回TRUE,失败返回FALSE。
d、等待网络事件来触发事件句柄的工作状态:
DWORDWSAWaitForMultipleEvents(DWORDcEvents,
constWSAEVENTFAR*lphEvents,BOOLfWaitAll,
DWORDdwTimeout,BOOLfAlertable);
lpEvent为事件句柄数组的指针
cEvent为为事件句柄的数目,其最大值为WSA_MAXIMUM_WAIT_EVENTS
fWaitAll指定等待类型:
TRUE:
当lphEvent数组重所有事件对象同时有信号时返回;
FALSE:
任一事件有信号就返回。
dwTimeout为等待超时(毫秒)
fAlertable为指定函数返回时是否执行完成例程
对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去
预声明值WSA_WAIT_EVENT_0,得到具体的引用值。
例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index-WSA_WAIT_EVENT_0];
e、判断网络事件类型:
intWSAEnumNetworkEvents(SOCKETs,
WSAEVENThEventObject,LPWSANETWORKEVENTSlpNetworkEvents);
s为套接字
hEventObject为需要重设的事件对象
lpNetworkEvents为记录网络事件和错误代码,其结构定义如下:
typedefstruct_WSANETWORKEVENTS{
longlNetworkEvents;
intiErrorCode[FD_MAX_EVENTS];
}WSANETWORKEVENTS,FAR*LPWSANETWORKEVENTS;
f、关闭事件对象句柄:
BOOLWSACloseEvent(WSAEVENThEvent);
调用成功返回TRUE,否则返回FALSE。