套接字是传输提供程序的句柄,是一个独立类型,SOCKET类型
socket WSASocket这两个函数可以用来创建套接字
- SOCKET socket(
- int af,
- int type,
- int protocol
- );
服务器API函数
1.绑定 bind
一旦创建了某种协议的套接字,必须将套接字绑定到一个已知的地址
- int bind(
- SOCKET s,
- const struct sockaddr FAR* name,
- int namelen
- );
- //s是等待客户连接的那个套接字
- SOCKET s;
- SOCKADDR_IN tcpaddr;
- int port = 5050;
- s = socket(AF_INET, SOCK_STREAM, IPPORTO_TCP);
- tcpaddr.sin_family = AF_INET;
- tcpaddr.sin_port = htons(port);
- tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));
一旦出错,bind返回SOCKET_ERROR
2.监听 listen
接下来要做的是将套接字置于监听模式。
- int listen(
- SOCKET s;//被绑定的套接字
- int backlog//指定了被搁置的连接最大队列长度
- );
3.接收连接 accept
accept WSAAccept AcceptEx可以接受连接
- SOCKET accept(
- SOCKET s,//被绑定的套接字,它处于监听模式
- struct sockaddr FAR* addr,//有效的SOCKADDR_IN地址
- int FAR* addrlen//SOCKADDR_IN结构长度
- );
编写一个能够接受TCP/IP连接的服务器
- #include
- void main(void)
- {
- WSAData wsaData;
- SOCKET ListenSocket;
- SOCKET NewSocket;
- SOCKADDR_IN ServerAddr;
- SOCKADDR_IN ClientAddr;
- int port = 5050;
- //初始化winsock版本
- WSAStartup(MAKEWORD(2,2), &wsaData);
- ListenSocket = socket(AF_INET, SOCK_STREAM, IPPORTO_TCP);
- ServerAddr.sin_family = AF_INET;
- ServerAddr.sin_port = htons(port);
- ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(ListenSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr));
- listen(ListenSocket, 5);
- NewSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddr, &ClientAddrLen);
- //..
- closesocket(NewSocket);
- closesocket(ServerSocket);
- 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函数来完成
- int connect(
- SOCKET s,//即将在上面建立连接的有效的TCP套接字
- const struct sockaddr FAR* name,
- int namelen
- );
演示一个客户端:
- #include
- void main(void)
- {
- WSADATA wsaData;
- SOCKET s;
- SOCKADDR_IN addr;
- int port = 5050;
- WSAStartup(MAKEWORD(2,2), &wsaData);
- s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = inet_addr("123.123.123.123");
- //...
- connect(s, (SOCKADDR*)&addr, sizeof(addr));
- closesocket(s);
- WSACleanup();
- }
数据传输
发送数据API send WSASend函数
接收数据API recv WSARecv函数
必须牢记着一点:所有关系到收发数据的缓冲区都属于简单的char类型,即面向字节的数据。
所有收发函数返回的错误都是SOCKET_ERROR,可以用WSAGetLastError来获得错误信息。
- int send(
- SOCKET s,//用于发送数据的套接字
- const char FAR* buf,//指向字符数据的缓冲区指针,缓冲区中包含发送的数据
- int len,//缓冲区内的字符数
- int flags
- );
- //顺利的情况下,send将返回发送的字节数,若发送错误则返回SOCKET_ERRPR
- int WSASend(
- SOCKET s,//连接会话的套接字
- LPWSABUF lpBuffer,//指向一个或多个WSABUF结构的指针
- DWORD dwBufferCount,//指明传递的WSABUF的数量
- LPDWORD lpNumberOfBytesSend,//指向DOWRD指针,其中包含已发送的字节总数
- DWORD dwFlags,
- LPWSAOVERLAPPED lpOverLapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
- );
- //lpNumberOfBytesSend设为写入的字节数,执行成功是该函数返回0,否则返回SOCK_ERROR
发送函数 WSASendDisconnect
这个函数很特殊,一般不用
- int WSASendDisconnect(
- SOCKET s,
- LPWSABUF lpOutboundDisconnectData
- );
函数WSASendDisconnect起初将套接字置于关闭状态,并发送端口的数据
在建立连接的套接字上通过recv来接收数据
- int recv(
- SOCKET s,//准备用来接收数据的套接字
- char FAR* buf,//用于接收数据的字符缓冲区
- int len,//准备接受的字节数或者缓冲区的长度
- int flags
- );
尽量把所有的数据都复制到自己的缓冲区中,并在那里操作数据。
当挂起数据大于所提供的缓冲区时,缓冲区会尽量的让数据填满,这时,recv调用会产生WSAEMSGSIZE错误。注意,消息大小的错误是在面向消息的协议时发生,而流协议则把传入的数据缓存下来,并尽量返回应用程序所需要的数据,即使挂起的数据比提供的缓冲区大,因此对于流协议就不会碰到WSAEMSGSIZE错误。
- int WSARecv(
- SOCKET s,//建立连接的套接字
- LPWSABUF lpBuffers,//用来接收数据的缓冲
- DWORD dwBufferCount,
- LPWORD lpNumberOfBytesRecvd, //指向这个函数调用从收到的字节数
- LPWORD lpFlags,
- LPWSAOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
- );
中断连接
一旦完成套接字连接,就必须关闭它,并释放关联到这个套接字上的所有资源。先用shutdown关闭连接,在利用closesocket释放关联到套接字上的资源。
- int shutdown(
- SOCKET s,
- int how
- );
- //how 可以是SD_RECEIVE,SD_SEND,SD_BOTH
- //SD_RECEIVE表示不允许再调用接收函数
- //SD_SEND表示不允许再调用发送函数
- //SD_BOTH表示取消两端的收发操作
closesocket 用来关闭套接字
- int closesocket(SOCKET s);