赞
踩
IP:port
1. IP
2. port
3. protocol (协议)
(IP, port, protocol)
和客户端的套接字 (IP, port, protocol)
可以通过套接字编程连接起来。Windows
有 WinSOCKET
编程,Linux
有 linux socket
编程,两者不兼容。VS 2019
<winsock2.h>
#include <iostream>
#include <winsock2.h>
#include <string>
// 使用 <winsock2.h>,需要链接的动态链接库 "Ws2_32.lib"
#pragma comment(lib, "Ws2_32.lib")
<winsock.h>
,使用较新的 <winsock2.h>
// 初始化 DLL
WORD sockVersion = MAKEWORD(2, 2);
WSADATA wsdata;
if (WSAStartup(sockVersion, &wsdata) != 0) {
return EXIT_FAILURE;
}
WORD
: 一个宏定义,一个 无符号短整型
typedef unsigned short WORD;
MAKEWORD
: 一个宏定义,获取 Windows Sockets 规范的版本
,(2, 2)
说明获取 2.2
版本,到 2021-01-10
最高为 2.2
版本#define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
WSADATA
: 一个结构体,存放 windows socket
初始化信息,具体如下(看注释):typedef struct WSAData { WORD wVersion; // 将使用的 Winsock 版本号 WORD wHighVersion; // 载入的 Winsock 动态库支持的最高版本,高字节代表次版本,低字节代表主版本 #ifdef _WIN64 // 如果是 64 位 unsigned short iMaxSockets; // 最大数量的并发 Sockets,其值依赖于可使用的硬件资源 unsigned short iMaxUdpDg; // 数据报的最大长度 char FAR * lpVendorInfo; // 为 Winsock 实现而保留的制造商信息 char szDescription[WSADESCRIPTION_LEN+1]; // 由特定版本的 Winsock 设置,实际上没有太大用处。 char szSystemStatus[WSASYS_STATUS_LEN+1]; // 同上 #else char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; #endif } WSADATA, FAR * LPWSADATA;
注释参考: https://blog.csdn.net/huachizi/article/details/89401476
WSAStartup(sockVersion, &wsdata)
: 重点,通过进程启动 Winsock DLL
的使用。int WSAAPI WSAStartup(
_In_ WORD wVersionRequested,
_Out_ LPWSADATA lpWSAData
);
如果成功,WSAStartup
函数将返回 0
。否则,它将返回下面列出的错误代码之一。
1. 图示流程
中流程图的操作#include <iostream> #include <winsock2.h> #include <string> #pragma comment(lib, "Ws2_32.lib") using namespace std; int main() { // 初始化 DLL WORD sockVersion = MAKEWORD(2, 2); WSADATA wsdata; if (WSAStartup(sockVersion, &wsdata) != 0) { return EXIT_FAILURE; } // 创建套接字 SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (serverSocket == INVALID_SOCKET) { cout << "create socket failure!!" << endl; return EXIT_FAILURE; } // 绑定套接字 sockaddr_in socketAddr; socketAddr.sin_family = AF_INET; socketAddr.sin_port = htons(8080); socketAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(serverSocket, (sockaddr*)&socketAddr, sizeof(socketAddr)) == SOCKET_ERROR) { cout << "bind error!" << endl; return EXIT_FAILURE; } // 监听 if (listen(serverSocket, 10) == SOCKET_ERROR) { cout << "listen error!!" << endl; } // 接收客户端套接字 SOCKET clientSocket; sockaddr_in client_sin; // store message int len = sizeof(client_sin); cout << "waitting to connect......" << endl; clientSocket = accept(serverSocket, (sockaddr*)&client_sin, &len); if (clientSocket == INVALID_SOCKET) { cout << "accept error" << endl; return EXIT_FAILURE; } else { cout << "get a connection: " << inet_ntoa(client_sin.sin_addr) << endl; } // 看流程图,循环 while (true) { // 接收消息 const size_t messageLen = 1000; char recClientMsg[messageLen]; int num = recv(clientSocket, recClientMsg, messageLen, 0); // 截断数组 recClientMsg[num] = '\0'; if (num > 0) { cout << "Client say: " << recClientMsg << endl; } // 退出 if (strcmp(recClientMsg, "exit()") == 0) { break; } // 发送数据 cout << "Input message you want to send to client:" << endl; string data; getline(cin, data); const char* sendClientMsg; sendClientMsg = data.c_str(); send(clientSocket, sendClientMsg, data.size(), 0); // 退出 if (strcmp(sendClientMsg, "exit()") == 0) { break; } } closesocket(clientSocket); closesocket(serverSocket); WSACleanup(); return EXIT_SUCCESS; }
#include <winsock2.h> #include <iostream> #include <string> using namespace std; #pragma comment(lib, "ws2_32.lib") int main() { // 初始化 WORD sockVersion = MAKEWORD(2, 2); WSADATA data; if (WSAStartup(sockVersion, &data) != 0) { return EXIT_SUCCESS; } // 创建客户端套接字 SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { cout << "Socket error" << endl; return EXIT_FAILURE; } // 连接服务端 sockaddr_in sock_in; sock_in.sin_family = AF_INET; sock_in.sin_port = htons(8080); sock_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (connect(clientSocket, (sockaddr*)&sock_in, sizeof(sock_in)) == SOCKET_ERROR) { cout << "Connect error" << endl; return EXIT_FAILURE; } else { cout << "Connect successfully." << endl; } // 看流程图,循环 while (true) { // 发送信息 cout << "Input message you want send:" << endl; string data; getline(cin, data); const char* sendServerMsg; sendServerMsg = data.c_str(); send(clientSocket, sendServerMsg, strlen(sendServerMsg), 0); // 判断结束循环 // 退出 if (data == "exit()") { break; } // 接收信息 const size_t messageLen = 1000; char revServerMsg[messageLen]; int num = recv(clientSocket, revServerMsg, messageLen, 0); // 截断数组 revServerMsg[num] = '\0'; if (num > 0) { cout << "Sever say:" << revServerMsg << endl; } // 判断结束循环 // 退出 if (strcmp(revServerMsg, "exit()") == 0) { break; } } // 关闭客户端套接字 closesocket(clientSocket); WSACleanup(); return EXIT_SUCCESS; }
Alt
+ P
+ P
(Alt
按住, P
按两下):一问一答
式通信#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 4096 #define PORT 6666 int main(int argc, char **argv) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; int n; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("create socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } if (listen(listenfd, 10) == -1) { printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } printf("======waiting for client's request======\n"); while (1) { if ((connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1) { printf("accept socket error: %s(errno: %d)", strerror(errno), errno); continue; } n = recv(connfd, buff, MAXLINE, 0); buff[n] = '\0'; printf("recv msg from client: %s\n", buff); close(connfd); } close(listenfd); }
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 4096 #define PORT 6666 int main(int argc, char **argv) { int sockfd, n; char recvline[MAXLINE], sendline[MAXLINE]; struct sockaddr_in servaddr; if (argc != 2) { printf("usage: ./client <ipaddress>\n"); exit(0); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("create socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { printf("inet_pton error for %s\n", argv[1]); exit(0); } if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("connect error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } printf("send msg to server: \n"); fgets(sendline, MAXLINE, stdin); if (send(sockfd, sendline, strlen(sendline), 0) < 0) { printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } close(sockfd); exit(0); }
g++ -o server server.c
# 生成 server 执行文件
./server
6666
占用[chen@hecs-70768 socket]$ lsof -i:6666
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
server 11795 chen 3u IPv4 4725561 0t0 TCP *:ircu-2 (LISTEN)
g++ -o client client.c
# 生成 client 执行文件
# xxx.xxx.xxx.xxx 是服务端的 IP,后面会提示你输入文字,在这里先不输入:
zhiyong@LAPTOP-OC4RD91F:/mnt/d/Computer/network/socket$ ./client xxx.xxx.xxx.xxx
send msg to server:
会显示服务端的端口显示ESTABLISHED
[chen@hecs-70768 socket]$ lsof -i:6666
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
server 11795 chen 3u IPv4 4725561 0t0 TCP *:ircu-2 (LISTEN)
server 11795 chen 4u IPv4 4727103 0t0 TCP hecs-70768:ircu-2->117.61.106.77:36486 (ESTABLISHED)
客户端执行 ./client xxx.xxx.xxx.xxx
的时候,首先执行 connect
,在内核中就是执行 TCP 三次握手。
客户端发起 connect
,服务端一直处于 listen
状态,然后在 accept
,在接收到客户端的 connect
后,返回一个客户端的套接字。然后这个客户端发送的数据,就是通过这个套接字去读取。读取的方法跟打开文件的 IO 一样。
TIME_WAIT
模式lsof -i:6666
查看客户端的端口,也可以通过抓三次握手的报文来获取端口。客户端发起连接的端口为 59438
,使用命令netstat -a | grep 59438
查看,如下:tcp 0 0 hecs-70768:ircu-2 hecs-70768:59438 TIME_WAIT
服务端中的 accept
函数返回一个客户端套接字 clientFd
,这个跟客户端的IP、端口相关。
理论上世界上所有的 IP 都可以作为客户端,每个客户端有 65536 个端口,而全世界的 IP 约有 四十亿个地址,所以有的文章说一台服务器最多有 四十亿个✖65546
。但是一台服务器需要一个监听套接字 serverFd
,当有一个客户端连接进来的时候,需要使用 accept
接收并建立一个套接字 clientFd
,而 accept
返回值是 int
类型,一般都大于 0
。而除去标准输入输出的 0, 1, 2
和自身的监听套接字serverFd
外,所以理论上最多应该是建立 int
最大值 - 4 的连接数。同时, 因为每条连接占内存,实际上一般就一百多万条就搞不住了。
close
操作,只要有一方发起,就会进行四次握手,这里说的发起连接是指:客户端就不用说了,就是指自身的套接字。服务端指的是accept
得到的客户端套接字clientFd
,而不是服务端的套接字serverFd
。如果关闭了服务端的套接字serverFd
,相当于服务端挂掉了。#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 4096 #define PORT 6666 int main(int argc, char **argv) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; int n; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("create socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } if (listen(listenfd, 10) == -1) { printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } printf("======waiting for client's request======\n"); // while (1) { if ((connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1) { printf("accept socket error: %s(errno: %d)", strerror(errno), errno); // continue; } n = recv(connfd, buff, MAXLINE, 0); buff[n] = '\0'; printf("recv msg from client: %s\n", buff); close(connfd); // 服务端先发起 close } close(listenfd); // 马上跟着就关闭了服务端的套接字,相当于服务端挂机了 }
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 4096 #define PORT 6666 int main(int argc, char **argv) { int sockfd, n; char recvline[MAXLINE], sendline[MAXLINE]; struct sockaddr_in servaddr; if (argc != 2) { printf("usage: ./client <ipaddress>\n"); exit(0); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("create socket error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { printf("inet_pton error for %s\n", argv[1]); exit(0); } if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { printf("connect error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } printf("send msg to server: \n"); fgets(sendline, MAXLINE, stdin); if (send(sockfd, sendline, strlen(sendline), 0) < 0) { printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } sleep(30); // 在这里等待 30s close(sockfd); exit(0); }
结果(服务端:ircu-2
,客户端:594381
):
[chen@hecs-70768 ~]$ netstat -a | grep 70768
tcp 0 0 hecs-70768:ssh 117.61.106.77:36482 ESTABLISHED
tcp 1 0 hecs-70768:59438 hecs-70768:ircu-2 CLOSE_WAIT
tcp 0 0 hecs-70768:ircu-2 hecs-70768:59438 FIN_WAIT2
tcp 0 36 hecs-70768:ssh 117.61.106.77:36526 ESTABLISHED
服务端发起关闭,发包 FIN
,客户端回复 ACK
,接着客户端准备发送 FIN
在这个过程,马上关闭了 服务端套接字,服务端没有回复 ACK
,那么服务端就处于FIN_WAIT2
(因为是服务端主动发起连接的)。而客户端处于CLOSE_WAIT
状态。由于服务端没有回复 ACK
,客户端就重发它的 FIN
,一共重发 5 词(这个大概看系统)。还是没有回复(因为服务端挂了),此时客户端发起一个 RST
,将连接断开。
之后服务端的连接就关闭了,就进入到TIME_WAIT
状态。
tcp 0 0 hecs-70768:ircu-2 hecs-70768:59438 TIME_WAIT
所以,谁发起 close
连接,谁就有FIN_WAIT2
、TIME_WAIT
的状态,而对端就有 CLOSE_WAIT
状态
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。