第二个参数指定要创建的套接字类型,流套接字类型为 SOCK_STREAM 、数据报套接字类型为 SOCK_DGRAM 、原始套接字 SOCK_RAW (WinSock接口并不适用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部);
第三个参数指定应用程序所使用的通信协议。此参数可以指定单个协议系列中的不同传输协议。在Internet通讯域中,此参数一般取值为0,系统会根据套接字的类型决定应使用的传输层协议。
memset (& serv_addr , 0 , sizeof ( serv_addr )); //每个字节都用0填充
serv_addr . sin_family = AF_INET ; //使用IPv4地址,指定协议族
serv_addr . sin_addr . s_addr = inet_addr ( "127.0.0.1" ); //具体的IP地址: inet_addr()的功能是将一个点分十进制的IP转换成一个长整数型数(u_long类型),服务端的话IP地址可以为指定为任意IP地址:INADDR_ANY
serv_addr . sin_port = htons ( 1234 ); //端口:htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处。
bind ( serv_sock , ( struct sockaddr *)& serv_addr , sizeof ( serv_addr ));//将套接字与目的地址绑定起来
socklen_t clnt_addr_size = sizeof ( clnt_addr );
int clnt_sock = accept ( serv_sock , ( struct sockaddr *)& clnt_addr , & clnt_addr_size );
如果客户端有连接请求,必须使用下述函数来接受客户端的请求。
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);
addr用于存放客户端的地址,addrlen在调用函数时被设置为addr指向区域的长度,在函数调用结束后被设置为实际地址信息的长度。本函数会阻塞等待直到有客户端请求到达。
返回值是一个新的套接字描述符,它代表的是和客户端的新的连接,这个套接字就是连接套接字,包含的是客户端的ip和port信息 ,而参数中的SOCKET s包含的是服务器的ip和port信息 。(当然这个new_socket会从serv_sock中继承 服务器的ip和port信息,两种都有了,就组成了一个套接字对,注意服务端与客户端是通过套接字对联系的,TCP必须查看套接字对的所有4个元素(客户端/服务端的IP/port)才能确定由哪个端点接收某个达到的分组)。
系统调用 accept() 会有点古怪的地方的!你可以想象发生 这样的事情:有人从很远的地方通过一个你在侦听 (listen()) 的端口连接 (connect()) 到你的机器。它的连接将加入到等待接受 (accept()) 的队列 中。你调用 accept() 告诉它你有空闲的连接。它将返回一个新的套接字文 件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口, 新的在准备发送 (send()) 和接收 ( recv()) 数据。这就是这个过程!
也就是说,在连接建立后,客户端用发出连接的那个SOCKET向服务器发数据,是发给服务器新创建的SOCKET,而不是服务器的监听SOCKET。服务器的监听SOCKET永远只是用来接受连接请求。 这就好比你去吃饭,饭馆门口有迎宾小姐(监听SOCKET)看到你来后和你打招呼,然后(ACCEPT)找来一个新的服务员(NEW SOCKET)来接待你,然后守在门口继续监听下一个。监听的小姐走了,接待你的服务员当然不受影响。
建立连接套接字后就可以开始客户端与服务端的联系
注意send/recv 及write/read的区别,这里稍后再整理吧。
服务端
char str [] = "Hello World!" ;write ( clnt_sock , str , sizeof ( str ));//通过连接套接字对发送消息
客户端
char buffer [ 40 ];read ( sock , buffer , sizeof ( buffer )- 1 );//通过连接套接字对接受消息
最后通讯结束,关闭套接字对
close ( clnt_sock );close ( serv_sock );//注意服务端还需要关闭监听套接字。