14.1Windows Sockets的实现
Windows Sockets是Microsoft Windows的网络程序设计,以动态链接库的形式提供给我们使用。
14.1.1套接字的类型
流式套接字(SOCK_STREAM):流式套接字实际上是基于TCP协议实现的。数据报套接字(SOCK_DGRAM):数据包套接字实际上是基于UDP协议实现的。原始套接字(SOCK_RAW)
14.1.2基于TCP(面向连接)的Socket编程
基于TCP的Socket编程的服务器端程序流程:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上(bind)。
3、将套接字设置为监听模式,准备接收客户请求(Listen)。
4、等待客户请求;请求到来时,接收连接请求;返回一个新的此次连接的套接字(accept)。
5、用返回的套接字和客户端进行通信(send/recv)。
6、返回,等待另一客户的请求。
7、关闭套接字。
基于TCP的Socket编程的客户端程序流程:
1、创建套接字(socket)。
2、向服务器发送连接请求(connect)。
3、和服务器端进行通信(send/recv)。
4、关闭套接字。
客户端并不需要调用bind函数。因为服务器需要接收客户端的请求,所以必须要告诉本地主机服务器的IP和端口;但是当客户端发起连接,服务器接受后,服务器就保存了客户端的IP和端口。
14.1.3基于UDP(面向无连接)的Socket编程
我们把服务器端也叫接收端,客户端叫发送端。
服务器端的编写:
1、创建套接字(socket)。
2、将套接字绑定到一个本地地址和端口上(bind)。
3、等待接收数据(recvfrom)。
4、关闭套接字。
虽然面向无连接,但是数据接收需要相应的IP和端口,服务器端仍然需要bind函数。
客户端的编写;
1、创建套接字(socket)。
2、向服务器发送数据(sendto).
3、关闭套接字。
套接字相当于电话机,IP相当于总机号码,而端口号相当于分机号码
14.2相关函数
14.2.1WSAStartup函数
该函数有两个功能:一是加载套接字库,二是进行套接字库的版本协商。
int WSAStartup (
WORD wVersionRequested,
LPWSADATA lpWSAData
);
第一个参数指定要加载的Winsock库的版本,可以利用MAKEWORD(x,y)宏获得wVersionRequested的正确值。x是高位字节指定Winsock库的副版本,y是低字节则是主版本。
第二个参数指向WSADATA结构的指针,WSAStartup函数用其加载的库版本的有关信息填在这个结构中。
对于WSAStartup函数的成功调用(即成功加载Winsock动态库后),在最后都对应一个WSACleanUp调用,以便释放为该应用程序分配的资源,终止对Winsock动态库的使用。
14.2.2socket函数
加载了套接字库之后就可以调用socket函数创建套接字了:
SOCKET socket (int af,int type,int protocol);
第一个参数指定地址族,对于TCP/IP协议的套接字,它只能是AF_INET.
第二参数指定Socket类型,对于1.1版本,只支持流式套接字和数据报套接字。
第三个参数指定相关协议,若为0表示会自动选择一个合适的协议。
如果调用成功,就会返回一个新的SOCKET类型套接字描述符;调用失败,返回一个INVALID_SOCKET值,错误信息通过WSAGetLastError函数返回。
14.2.3bind函数
int bind (SOCKET s,const struct sockaddr FAR* name,int namelen
);
第一个参数指定要绑定的套接字;第二个参数指定该套接字的本地地址信息,这是一个指向sockaddr结构的指针变量;第三个参数指定该地址结构的长度。
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
第一个字段指定地址家族,必须设置为AF_INET;第二个指定该区域中与协议相关的具体地址信息。
在基于TCP/IP的socket编辑过程中,可以用sockadd_in结构替换sockaddr,以方便我们填写地址信息。
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
第二个参数指定要分配给套接字的端口;第三个参数指定要分配给套接字的IP地址;第四个参数是一个填充数,函数调用成功返回0
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
通常利用这个结构将一个点分十进制格式的IP地址转换为u_long类型,并将结果赋给成员S_addr。
14.2.4inet_addr和inet_ntoa函数
将地址指定为INADDR_ANY,将允许一个独立应用接受发自多个接口的回应。如果我们想让套接字使用多个IP地址中一个地址,就必须指定实际地址,用inet_addr函数来实现:
unsigned long inet_addr (const char FAR * cp);
该参数是一个字符串,指定了点分十进制格式表示的IP地址。返回一个适合分配给S_addr的u_long类型的数值。
inet_ntoa函数会完成相反的转换:
char FAR * inet_ntoa (struct in_addr in);
接受一个in_addr结构体类型的参数并返回一个点分十进制格式表示的IP地址字符串。
14.2.5listen函数
将套接字设置为监听模式。
int listen (SOCKET s,int backlog);
第一个参数是套接字描述符;第二个参数是等待连接队列的最大长度,如果将其设置为2,当三个请求同时到达时,前两个连接请求放到等待请求连接队列中,第三个连接请求就会被拒绝。
14.2.6accept函数
接受客户端发送的连接请求:
SOCKET accept (SOCKET s,struct sockaddr FAR* addr, int FAR* addrlen);
第一个参数是套接字描述符;第二个参数接收连接实体的地址;第三个参数返回包含地址信息的长度。
14.2.7send函数
通过已建立的套接字发送数据:
int send(SOCKET s,const char FAR * buf, int len,int flags);
第一个参数是一个已建立的套接字;第二个参数指要传递的数据;第三个参数传送数据的长度;第四个参数一般设置为0.
14.2.8recv函数
从一个已连接的套接字接收数据。
int recv (SOCKET s,char FAR* buf,int len,int flags);
参数与send函数一致。
14.2.9connect函数
将一个特定的套接字建立连接:
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);
第二个参数连接的服务器地址信息;第三个参数指定服务器端地址的长度。
14.2.10recvfrom函数
接收一个数据报信息并保存源地址
int recvfrom (SOCKET s,char FAR* buf,int len,int flags,struct sockaddr FAR* from,int FAR* fromlen);
第二个、第三个参数接收数据以及它的长度,第四个参数与send函数最后一个参数一致,第五个参数接收发送方的地址信息,最后一个参数返回地址结构的大小。
14.2.11sendto函数
向一个特定的目标方发送数据
int sendto (SOCKET s,const char FAR * buf,int len,int flags, const struct sockaddr FAR * to, int tolen);
第五个参数指定目标套接字的地址,最后一个参数指定地址的长度。
4.2.12htons和htonl函数
u_short htons (u_short hostshort );
将一个u_short类型的值从主机字节顺序转换为TCP/IP网络字节顺序。
与此类似的还有一个函数
u_long htonl (u_long hostlong);