实现两台或多台已经联网的计算机互相交换数据的行为,就是网络编程.
我们日常使用的操作系统已经为我们提供了socket, 不需要熟悉网络数据传输的原理,
也能掌握网络编程.
windows中的socket与Linux中的有何区别?
- 相同
- Linux通常会用文件描述符来表示或区分已经打开了的文件;
- windows通过文件句柄的方式来表示,和上述的Linux的文件描述符是类似的概念;
- 不同
- Linux的一切都是文件,所以网路连接也是一个文件.
- Windows则会将socket和文件区分开来,因此在Windows中socket有针对性设计的数据传输函数.
- SOCK_STREAM
面向连接的套接字(Stream Sockets),是对SOCK_STREAM的说明.
使用了TCP协议,有自己的纠错机制.
浏览器使用的http协议就是基于面向连接的套接字
- SOCK_DGRAM
无连接的套接字(Datagram Sockets),也叫数据报格式套接字.
它的传输效率相对于SOCK_STREAM要高,但对数据的校验工作较少.
视频聊天和语音视频大多是采用无连接套接字来传输数据的.
- SOCK_RAW
原始套接字(raw-protocol interface).
保存了数据包的完整IP头,可以通过它来对数据进行分析.
网络安全产品通常使用此类型,如MAC地址扫描器,网络嗅探器等产品.
TCP套接字
- 服务端
#include#include #pragma comment(lib,"ws2_32.lib") using namespace std; int main() { //初始化套接字库 WORD wVersion; WSADATA wsaData; int err; wVersion = MAKEWORd(1, 1); err = WSAStartup(wVersion, &wsaData); //检查 if (err != 0) { return err; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { //清理套接字库 WSACleanup(); return -1; } //创建套接字 SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0); //准备绑定的信息 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //绑定到本机 bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); //监听 listen(sockSrv, 10); cout << "server start at prot: 6000" << endl; SOCKADDR addrCli; int len = sizeof(SOCKADDR); char recvBuf[100]; char sendBuf[100]; while (true) { //接受连接请求 SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len); sprintf_s(sendBuf,100, "hello"); send(sockConn, sendBuf, strlen(sendBuf) + 1, 0); //接受或发送数据 recv(sockConn, recvBuf, 100, 0); std::cout << recvBuf << std::endl; //关闭套接字 closesocket(sockConn); } //关闭套接字 closesocket(sockSrv); //清理套接字库 WSACleanup(); system("pause"); return 0; }
- 客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include#include #pragma comment(lib,"ws2_32.lib") using namespace std; int main() { //初始化套接字库 WORD wVersion; WSADATA wsaData; int err; wVersion = MAKEWORd(1, 1); err = WSAStartup(wVersion, &wsaData); //检查 if (err != 0) { return err; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { //清理套接字库 WSACleanup(); return -1; } //创建tcp套接字 SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.0.131"); //addrSrv.sin_addr.S_un.S_addr = htons(6000);//大失误 addrSrv.sin_port = htons(6000); addrSrv.sin_family = AF_INET; //连接服务器 connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); char sendBuf[] = "world"; char recvBuf[100]; //发送数据到服务器 send(sockCli, sendBuf, strlen(sendBuf) + 1, 0); //接收服务器发送的数据 recv(sockCli, recvBuf, sizeof(recvBuf), 0); cout << recvBuf << endl; closesocket(sockCli); WSACleanup(); system("pause"); return 0; }
UDP套接字
- 服务端
#include#include #pragma comment(lib,"ws2_32.lib") using namespace std; int main() { //初始化套接字库 WORD wVersion; WSADATA wsaData; wVersion = MAKEWORd(1, 1); int err = WSAStartup(wVersion, &wsaData); //检查 if (err != 0) { cout << WSAGetLastError() << endl; return err; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { cout << WSAGetLastError() << endl; WSACleanup(); return -1; } //创建套接字 SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_port = htons(6002);//主机字节序转换 addrSrv.sin_family = AF_INET; //绑定到本机6002端口 bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); //接受请求,处理请求 SOCKADDR_IN addrCli; int len = sizeof(SOCKADDR); char sendBuf[] = "UDP Server ..."; char recvBuf[100]; cout << "Start UDP Server with port 6002 " << endl;; while (true) { //接收数据 recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len); cout << "Recv: " << recvBuf << endl; //发送数据 sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len); cout << "Send: " << sendBuf << endl; } //关闭套接字并清除套接字库 closesocket(sockSrv); WSACleanup(); system("pause"); return 0; }
- 客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include#include #pragma comment(lib,"ws2_32.lib") using namespace std; int main() { //初始化套接字库 WORD wVersion; WSADATA wsaData; wVersion = MAKEWORd(1, 1); int err = WSAStartup(wVersion, &wsaData); //检查 if (err != 0) { return err; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return -1; } //创建UDP套接字 SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_port = htons(6002); addrSrv.sin_family = AF_INET; SOCKADDR_IN addrCli; int len = sizeof(SOCKADDR); char sendBuf[] = "send from UDP client ..."; char recvBuf[100]; //发送数据到服务端并打印 cout << "send to server: " << sendBuf << endl; sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len); //接收服务端数据并打印 recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len); cout << "receve from: " << recvBuf << endl; //关闭套接字并清除套接字库 closesocket(sockCli); WSACleanup(); system("pause"); return 0; }
常用函数分析
- WSAStartup()函数
是Windows操作系统特有的函数.
在Windows系统中使用网络编程需要加载ws2_32.dll动态链接库.
使用这个dll之前需要调用WSAStartup()函数初始化动态库.
函数原型:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- socket()函数
程序使用socket函数来创建套接字,不管是Linux还是Windows都一样,区别是返回值不同.
Linux中socket函数的返回值是int,Windows里的返回值是SOCKET.
我这里练习的环境是windows,所以这里的代码例子都以Windows为准.
函数原型:
SOCKET socket(int af, int type, int protocol);
- bind()函数
创建完套接字之后需要确定套接字的各种属性,比如IP地址,端口等信息.
这些信息用一个sockaddr结构体变量存放.
函数原型:
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
- connect()函数
是客户端在创建完套接字后,用来建立连接的.
它的参数和bind相同.
函数原型:
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);
- listen()函数
在服务端绑定完套接字之后,还使用listen函数让套接字进入被动监听状态.
第二个参数是请求队列的最大长度.
函数原型:
int listen(SOCKET sock, int backlog);
- accept()函数
套接字通过listen函数进入被动监听状态之后,通过accept函数接收客户端请求.
它的参数跟listen和connect函数相同.
函数原型:
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
- send()函数
经过上面一系列的连接的目的,自然是为了实现服务端与客户端之间的发送和接收数据.
send函数是从服务端发送数据,第四个参数可以参考send recv函数中的flags参数
函数原型:
int send(SOCKET sock, const char *buf, int len, int flags);
- recv()函数
recv函数的功能是从服务端或客户端接收数据.
它和send函数的参数是一样的.
函数原型:
int recv(SOCKET sock, char *buf, int len, int flags);
总结
这篇博客内容只是简单的涵盖了socket编程需要用到的函数,
以及给出了简单的示例代码.对于socket编程入门还是有帮助的.
Windows操作系统拓展的套接字函数一般以WSA开头,它的意思是Windows socket API.常用的比如:
WSASend,WSARecv以及前面提到的WSACleanup等.



