套接字是传输提供程序的句柄,是一个独立类型,SOCKET类型

socket WSASocket这两个函数可以用来创建套接字

  1. SOCKET socket( 
  2.   int af, 
  3.   int type, 
  4.   int protocol 
  5. ); 

 

服务器API函数

1.绑定 bind

一旦创建了某种协议的套接字,必须将套接字绑定到一个已知的地址

  1. int bind( 
  2.   SOCKET s, 
  3.   const struct sockaddr FAR* name, 
  4.   int   namelen 
  5. ); 
  6. //s是等待客户连接的那个套接字 
  7.  
  8. SOCKET s; 
  9. SOCKADDR_IN tcpaddr; 
  10. int port = 5050; 
  11.  
  12. s = socket(AF_INET, SOCK_STREAM, IPPORTO_TCP); 
  13.  
  14. tcpaddr.sin_family = AF_INET; 
  15. tcpaddr.sin_port = htons(port); 
  16. tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
  17.  
  18. bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr)); 

一旦出错,bind返回SOCKET_ERROR

 

2.监听 listen

接下来要做的是将套接字置于监听模式。

  1. int listen( 
  2.   SOCKET s;//被绑定的套接字 
  3.   int backlog//指定了被搁置的连接最大队列长度 
  4. ); 

 

3.接收连接 accept

accept WSAAccept AcceptEx可以接受连接

  1. SOCKET accept( 
  2.   SOCKET s,//被绑定的套接字,它处于监听模式 
  3.   struct sockaddr FAR* addr,//有效的SOCKADDR_IN地址 
  4.   int FAR* addrlen//SOCKADDR_IN结构长度 
  5. ); 

 

编写一个能够接受TCP/IP连接的服务器

  1. #include  
  2. void main(void
  3.   WSAData wsaData; 
  4.   SOCKET  ListenSocket; 
  5.   SOCKET  NewSocket; 
  6.   SOCKADDR_IN ServerAddr; 
  7.   SOCKADDR_IN ClientAddr; 
  8.   int port = 5050; 
  9.   //初始化winsock版本 
  10.   WSAStartup(MAKEWORD(2,2), &wsaData); 
  11.  
  12.   ListenSocket = socket(AF_INET, SOCK_STREAM, IPPORTO_TCP); 
  13.   ServerAddr.sin_family = AF_INET; 
  14.   ServerAddr.sin_port = htons(port); 
  15.   ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
  16.    
  17.   bind(ListenSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)); 
  18.    
  19.   listen(ListenSocket, 5); 
  20.    
  21.   NewSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddr, &ClientAddrLen); 
  22.   //.. 
  23.  
  24.   closesocket(NewSocket); 
  25.   closesocket(ServerSocket); 
  26.   WSACleanup(); 

 

客户端API函数

TCP状态:对每一个套接字来说,它的初始状态都是CLOSED。若客户机初始化一个连接就会向服务器发送一个SYN包,同时客户机将套接字状态置为SYN_SENT。服务器收到一个SYN包后,会发出一个SYN_ACK包,客户机需要发送一个ACK包对它作出响应。此时客户机将套接字处于ESTABLISHED状态,如果服务器一直不发送SYN_ACK包,客户机就会超时,并返回CLOSED状态。

若服务器的套接字同本地接口及端口绑定起来,并在上面进行监听,那么套接字状态就是LISTEN。客户机试图与服务器连接时,服务器就会收到一个SYN包,并用一个SYN_ACK包作为回应,服务器套接字状态就会变成SYN_RCVD,最好客户机发出一个ACK包,它将使服务器套接字置于ESTABLISHED状态。

一旦应用程序处于ESTABLISHED状态,就可以通过两种方法关闭它。如果是应用程序来关闭,便叫做主动套接字关闭;否则就是被动的。若主动关闭,应用程序会发出一个FIN包。应用程序调用closesocket或shutdown时,会向通信对方发出一个FIN包,而且套接字状态将变为FIN_WAIT_1。正常情况下,通信对方会用一个ACK包作为回应,套接字状态将变为FIN_WAIT_2。如果通信对方也关闭了连接,它会发出一个FIN包,我们的机器则会以一个ACK包作为回应,并将套接字状态置为TIME_WAIT。

被动关闭时,应用程序会从对方那里收到一个FIN包,并用一个ACK包作为回应,此时应用程序的套接字会变为CLOSE_WAIT状态。由于对方已经关闭自己,它不再发送数据。而应用程序却不同,它能一直发送数据,直到它自己关闭连接为止。要想连接终端,应用程序需要发出自己的FIN包,令应用程序套接字置于LAST_ACK,应用程序从对方接收到一个ACK包后,它的套接字就会变成CLOSEED状态。

 

connect  

套接字连接通过调用connect,WSAConnect,ConnectEx函数来完成

  1. int connect( 
  2.   SOCKET s,//即将在上面建立连接的有效的TCP套接字 
  3.   const struct sockaddr FAR* name, 
  4.   int namelen 
  5. ); 

 

演示一个客户端:

  1. #include  
  2.  
  3. void main(void
  4.   WSADATA wsaData; 
  5.   SOCKET  s; 
  6.   SOCKADDR_IN addr; 
  7.   int port = 5050; 
  8.    
  9.   WSAStartup(MAKEWORD(2,2), &wsaData); 
  10.   s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
  11.   addr.sin_family = AF_INET; 
  12.   addr.sin_port = htons(port); 
  13.   addr.sin_addr.s_addr = inet_addr("123.123.123.123"); 
  14.   //... 
  15.   connect(s, (SOCKADDR*)&addr, sizeof(addr)); 
  16.   closesocket(s); 
  17.   WSACleanup(); 

 

数据传输

发送数据API send WSASend函数

接收数据API recv WSARecv函数

必须牢记着一点:所有关系到收发数据的缓冲区都属于简单的char类型,即面向字节的数据。

所有收发函数返回的错误都是SOCKET_ERROR,可以用WSAGetLastError来获得错误信息。

  1. int send( 
  2.   SOCKET s,//用于发送数据的套接字 
  3.   const char FAR* buf,//指向字符数据的缓冲区指针,缓冲区中包含发送的数据 
  4.   int len,//缓冲区内的字符数 
  5.   int flags 
  6. ); 
  7. //顺利的情况下,send将返回发送的字节数,若发送错误则返回SOCKET_ERRPR 

  1. int WSASend( 
  2.   SOCKET s,//连接会话的套接字 
  3.   LPWSABUF lpBuffer,//指向一个或多个WSABUF结构的指针 
  4.   DWORD  dwBufferCount,//指明传递的WSABUF的数量 
  5.   LPDWORD  lpNumberOfBytesSend,//指向DOWRD指针,其中包含已发送的字节总数 
  6.   DWORD  dwFlags, 
  7.   LPWSAOVERLAPPED lpOverLapped, 
  8.   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
  9. ); 
  10. //lpNumberOfBytesSend设为写入的字节数,执行成功是该函数返回0,否则返回SOCK_ERROR 

发送函数 WSASendDisconnect

这个函数很特殊,一般不用

  1. int WSASendDisconnect( 
  2.   SOCKET s, 
  3.   LPWSABUF lpOutboundDisconnectData 
  4. ); 

函数WSASendDisconnect起初将套接字置于关闭状态,并发送端口的数据

 

在建立连接的套接字上通过recv来接收数据

  1. int recv( 
  2.   SOCKET s,//准备用来接收数据的套接字 
  3.   char FAR* buf,//用于接收数据的字符缓冲区 
  4.   int len,//准备接受的字节数或者缓冲区的长度 
  5.   int flags 
  6. ); 

尽量把所有的数据都复制到自己的缓冲区中,并在那里操作数据。

当挂起数据大于所提供的缓冲区时,缓冲区会尽量的让数据填满,这时,recv调用会产生WSAEMSGSIZE错误。注意,消息大小的错误是在面向消息的协议时发生,而流协议则把传入的数据缓存下来,并尽量返回应用程序所需要的数据,即使挂起的数据比提供的缓冲区大,因此对于流协议就不会碰到WSAEMSGSIZE错误。

  1. int WSARecv( 
  2.   SOCKET s,//建立连接的套接字 
  3.   LPWSABUF lpBuffers,//用来接收数据的缓冲 
  4.   DWORD dwBufferCount, 
  5.   LPWORD lpNumberOfBytesRecvd, //指向这个函数调用从收到的字节数
  6.   LPWORD lpFlags, 
  7.   LPWSAOVERLAPPED lpOverlapped, 
  8.   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
  9. ); 

 

中断连接

一旦完成套接字连接,就必须关闭它,并释放关联到这个套接字上的所有资源。先用shutdown关闭连接,在利用closesocket释放关联到套接字上的资源。

  1. int shutdown( 
  2.   SOCKET s, 
  3.   int  how 
  4. ); 
  5. //how 可以是SD_RECEIVE,SD_SEND,SD_BOTH 
  6. //SD_RECEIVE表示不允许再调用接收函数 
  7. //SD_SEND表示不允许再调用发送函数 
  8. //SD_BOTH表示取消两端的收发操作 

closesocket 用来关闭套接字

  1. int closesocket(SOCKET s);