要进行网络通信编程,就要用到socket(套接字)。套接字代表一个通信端口,有地址,有端口号,可连接(按类型),可收,可发。
要进行socket编程,要为工程导入库文件,添加头文件,并在程序里加载套接字库。
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") //2.0版本
#include <winsock.h>
#pragma comment(lib, "wsock32.lib") //1.0版本
winsock是Windows平台下的网络套接字(Socket)接口规范。它从Unix平台的Berkele y(BSD)套接字方案借鉴了许多东西。Winsock是网络编程接口,而不是协议。建立Winsock2规范的主要目的是提供一个与协议无关的传送接口。使用winsock API,可以编写基于各种网络协议(如TCP、UDP、IP)的应用程序。实际上Windows下多数要访问网络的程序其底层都调用了winsock API。winsock2的winsock API的dll文件是ws2_32.dll。
服务端监听过程:
1. 加载套接字库,主要是版本,注意拼接次序。
WSADATA{ wVersion,wHighVersion, iMaxSockets\iMaxUdpDg };
0 == WSAStartup(MAKEWORD(1,2), &WSADATA) ; //载入WinSocket API dll库
w = MAKEWORD(a, b); a = LOBYTE(w); b = HIBYTE(w); //版本2.1,makeword(1,2)
l = MAKELONG(a, b); a = LOWORD(l); b = HIWORD(w);
2. 创建一个服务器端socket,这是一个监听套接字
INVALID_SOCKET(socket类) != socket(AF_INET\UNIX\…, SOCK_STREAM\DGRAM\…, 0); //通信端点
3. 创建一个地址结构体,用bind函数把socket和地址绑定,只有服务器端监听套接字需要绑定:
SOCKADDR_IN{ sin_family, //指定地址家族
sin_port = htons(short),
sin_addr.s_addr = htonl(INADDR_ANY)
= inet_addr("172.29.1.1")
} //inet_ntoa(long)
SOCKET_ERROR(-1) != bind(m_Socket, (struct sockaddr *)&SOCKADDR_IN, sizeof());
4. 设置为监听模式:
listen(m_Socket, SOMAXCONN); //放入等待连接队列,超出的直接丢弃
5. 等待客户端的连接请求:
real_conn_Socket = accept (m_Socket, (SOCKADDR*) &SOCKADDR_IN, &len);
//real_conn_Socket
◆当有客户端请求连接m_Socket,accept 函数就会执行。注意,accept函数返回一个real_conn_Socket,其实当成功连接后,与客户端custom_Socket连接的是real_conn_Socket,而不是m_Socket,进行数据传送的也是real_conn_Socket。简单的说,服务器端m_Socket只负责监听和接收连接请求。
6. 卸载套接字库:
WSACleanup();
客户端请求连接过程:
3. 向服务器端socket发出连接请求:
SOCKADDR_IN{ sin_family, //指定地址家族
sin_port = 服务器端监听套接字bind的端口,
sin_addr.s_addr = 服务器端监听套接字bind的IP
}
connect(custom_Socket, (SOCKADDR*)&SOCKADDR_IN, sizeof(SOCKADDR_IN));
◆客户端socket并不需要绑定IP地址和端口,服务器端socket需要绑定。如果服务器端socket不绑定,客户端socket又知道向哪个IP地址和端口发起连接请求呢?而系统会自动为客户端socket绑定一个随机的端口,服务器只要用accept函数返回的socket与客户端socket交换数据就行了,如果服务器需要查询客户端socket的IP和端口,可以查看accept函数的第二个参数记录的客户端socket地址信息。
然后是进行数据的传输,发送数据send:
建立连接后,服务器端的sockConn与客户端的sockClient就连接起来并且可以互相传输数据了,
int send(
SOCKET s, //连接socket,非监听socket
const char FAR *buf, //要发送数据buf的地址
int len, //buf的长度
int flags //一般设置为0即可。
);
接收数据使用recv函数:
int recv(
SOCKET s,
char FAR *buf, //要发送数据buf的地址。
int len, //要接收数据buf的长度
int flags //一般设置为0即可。
);
使用closesocket函数关闭其中一端socket后,连接断开发生的各种情况:
关闭socketA之后:
对socketA使用recv函数接收数据,和使用send函数发送数据,都会马上返回SOCKET_ERROR。
之后另一端socketB:
1.socketB第一次send函数能成功返回发送数据的大小,并不会返回SOCKET_ERROR 。但socketA是无法接收到的。之后socketB使用send函数返回的都是SOCKET_ERROR 。
2.socketB使用recv函数接收数据。如果socketB之前没有使用send函数,那么recv函数的返回值总是0。直到socketB调用过send函数之后,recv函数的返回值总是SOCKET_ERROR。
因此要考虑A端和B端如何协调关闭连接。socketA想要关闭连接,除了closesocket(socketA)之外,还要另外通知socketB,不然socketB还在那里傻傻的接收/发送数据。
如果不想显式的通知,socketB就要自己判断,如果socketB多次调用send函数总是返回SOCKET_ERROR或者socketB多次调用recv函数总是返回0或SOCKET_ERROR,那就要意识到连接很可能已经断开了。
数据的流向是单向的:
例如说数据只从socketA流向socketB(类似文件传输就是这样),那么socketA只会调用send函数,而socketB只会调用recv函数,这时候如果其中一方要停止数据的传输,就会有两种情况出现:
1.如果socketA的计算机不想发送数据而closesocket(socketA),由于socketB从来不调用send函数,因此socketB的recv函数总是返回0,那么socketB的计算机就要意识到socketA很可能已经关闭了,让socketB的计算机closesocket(socketB)。
2.如果socketB的计算机不想接收数据而closesocket(socketA),这时socketA继续调用send函数发送数据,第一次send还是成功的,但从第二次send开始就总会返回SOCKET_ERROR,但是socketA的计算机无法判断send函数返回SOCKET_ERROR是由socketB关闭造成的还是Asocket关闭造成的(因为socketA关闭后socketA调用send函数也是返回SOCKET_ERROR),因此socketA的计算机无法判断socketA关闭了没有,简单的解决方法是如果socketB的计算机不想接收数据,先不要关闭socketB,而是发通知给Asocket的计算机告诉它我不想收数据了,socketA的计算机收到通知后关闭socketA,这样情形就回到上面情况1去了,而且也知道socketA调用send函数返回SOCKET_ERROR肯定是由于socketA关闭造成的而不是由socketB关闭造成的。