目录
1--TCP/IP协议栈
2--TCP服务器端默认函数调用顺序
3--TCP客户端的默认函数调用顺序
4--Linux实现迭代回声服务器端/客户端
5--Windows实现迭代回声服务器端/客户端
6--TCP原理
7--Windows实现计算器服务器端/客户端
1--TCP/IP协议栈
TCP/IP协议栈共分 4 层,可以理解为数据收发分成了 4 个层次化过程;
链路层:
链路层是物理链接领域标准化的结果,专门定义LAN、WAN、MAN等网络标准;
IP层:
IP层用于解决数据传输过程中路径的选择问题;
TCP/IP层:
即传输层,用于解决数据传输的问题(数据顺序、可靠性等);
应用层:
程序员根据数据传输规则,编写规定的程序(例如Socket)来实现数据传输;
2--TCP服务器端默认函数调用顺序
一般 TCP 服务器端调用默认函数的顺序如下:socket() 创建 Socket → bind() 分配Socket 地址 → listen() 等待连接请求状态 → accept() 允许连接 → read()/write() 数据交换 → close() 断开连接;
调用 listen() 函数进入等待连接请求状态,只有服务器端调用了 listen() 函数,客户端才能进入可发出连接请求的状态;
#include <sys/socket.h>int listen(int sock, int backlog); // 成功时返回 0, 失败时返回 -1;// sock 表示希望进入等待连接请求状态的Socket的文件描述符
// backlog 表示连接请求等待队列的长度,即最多可以使多少个连接请求进入队列
服务器端调用 accept() 函数来受理客户端的连接请求,即受理等待队列中待处理的客户端连接请求;
#include <sys/socket.h>int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
// 成功时返回创建的Socket的文件描述符,失败时返回-1
3--TCP客户端的默认函数调用顺序
一般 TCP 客户端调用默认函数的顺序如下:socket() 创建 Socket → connect() 请求连接 → read()/write() 交换数据 → close() 断开连接;
在服务器端调用 listen() 函数创建连接请求等待队列后,客户端可通过调用 connect() 函数来请求连接;
#include <sys/socket.h>int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);// sock 表示客户端socket的文件描述符
// servaddr 保存了目标服务器端地址信息
// addrlen 第二个结构体参数 servaddr 的地址变量长度,以字节为单位
4--Linux实现迭代回声服务器端/客户端
服务器端:
// gcc echo_server.c -o echo_server
// ./echo_server 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024
void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[]){int serv_sock, clnt_sock;char message[BUF_SIZE];int str_len, i;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;if(argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);if(serv_sock == -1){error_handling("socket() error");}memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){error_handling("bind() error");}if(listen(serv_sock, 5) == -1){error_handling("listen() error");}clnt_adr_sz = sizeof(clnt_adr);for(i = 0; i < 5; i++){clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);if(clnt_sock == -1){error_handling("accept() error");}else{printf("Connected client %d \n", i+1);}while((str_len = read(clnt_sock, message, BUF_SIZE)) != 0){write(clnt_sock, message, str_len);}close(clnt_sock);}close(serv_sock);return 0;
}
客户端:
// gcc echo_client.c -o echo_client
// ./echo_client 127.0.0.1 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[]){int sock;char message[BUF_SIZE];int str_len;struct sockaddr_in serv_adr;if(argc != 3){printf("Usage : %s <IP> <port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if(sock == -1){error_handling("socket() error");}memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){error_handling("connect() error!");}else{puts("Connected.......");}while(1){fputs("Input message(Q to quit): ", stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message, "q\n") || !strcmp(message, "Q\n")){break;}write(sock, message, strlen(message));str_len = read(sock, message, BUF_SIZE-1);message[str_len] = 0;printf("Message from server: %s", message);}close(sock);return 0;
}
运行结果:
5--Windows实现迭代回声服务器端/客户端
服务器端:
// gcc echo_server_win.c -o echo_server_win -lwsock32
// echo_server_win 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 1024void ErrorHandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[]){WSADATA wsaData;SOCKET hServSock, hClntSock;char message[BUF_SIZE];int strLen, i;SOCKADDR_IN servAdr, clntAdr;int clntAdrSize;if(argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){ErrorHandling("WSAStartup() error!");}hServSock = socket(PF_INET, SOCK_STREAM, 0);if(hServSock == INVALID_SOCKET){ErrorHandling("socket() error");}memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = htons(atoi(argv[1]));if(bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){ErrorHandling("bind() error");}if(listen(hServSock, 5) == SOCKET_ERROR){ErrorHandling("listen() error");}clntAdrSize = sizeof(clntAdr);for(int i = 0; i < 5; i++){hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);if(hClntSock == -1){ErrorHandling("accept() error");}else{printf("Connected client %d \n", i + 1);}while((strLen = recv(hClntSock, message, BUF_SIZE, 0)) != 0){send(hClntSock, message, strLen, 0);}closesocket(hClntSock);}closesocket(hServSock);WSACleanup();return 0;
}
客户端:
// gcc echo_client_win.c -o echo_client_win -lwsock32
// echo_client_win 127.0.0.1 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 1024void ErrorHandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char *argv[]){WSADATA wsaData;SOCKET hSocket;char message[BUF_SIZE];int strLen;SOCKADDR_IN servAdr;if(argc != 3){printf("Usage: %s <IP> <port>\n", argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){ErrorHandling("WSAStartup() error!");}hSocket = socket(PF_INET, SOCK_STREAM, 0);if(hSocket == INVALID_SOCKET){ErrorHandling("socket() error");}memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = inet_addr(argv[1]);servAdr.sin_port = htons(atoi(argv[2]));if(connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){ErrorHandling("connect() error!");}else{puts("Connected........");}while(1){fputs("Input message(Q to quit): ", stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message, "q\n") || !strcmp(message, "Q\n")){break;}send(hSocket, message, strlen(message), 0);strLen = recv(hSocket, message, BUF_SIZE - 1, 0);message[strLen] = 0;printf("Message from server: %s", message);}closesocket(hSocket);WSACleanup();return 0;
}
测试结果:
6--TCP原理
I/O缓冲:
当调用 write() 函数后并不会立即传输数据,而是将数据移至输出缓冲;
同样当调用 read() 函数后也并不会马上接收数据,而是将数据移至输入缓冲;
TCP会控制数据流,使用滑动窗口协议(参考博主之前的笔记:TCP流量控制)来限制数据传输不会超过输入缓冲的大小;
TCP 协议使用三次握手来建立连接,使用四次挥手来断开连接;(具体分析参考书中图解)
7--Windows实现计算器服务器端/客户端
服务器端:
// gcc op_server_win.c -o op_server_win -lwsock32
// op_server_win 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 1024
#define OPSZ 4void ErrorHandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}int calculate(int opnum, int opnds[], char op){int result = opnds[0], i;switch(op){case '+':for(i = 1; i < opnum; i++) result += opnds[i];break;case '-':for(i = 1; i < opnum; i++) result -= opnds[i];break;case '*':for(i = 1; i < opnum; i++) result *= opnds[i];break;}return result;
}int main(int argc, char* argv[]){WSADATA wsaData;SOCKET hServSock, hClntSock;char opinfo[BUF_SIZE];int result, opndCnt, i;int recvCnt, recvLen;SOCKADDR_IN servAdr, clntAdr;int clntAdrSize;if(argc != 2){printf("Usage: %s <port>\n", argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){ErrorHandling("WSAStartup() error!");}hServSock = socket(PF_INET, SOCK_STREAM, 0);if(hServSock == INVALID_SOCKET){ErrorHandling("socket() error!");}memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = htonl(INADDR_ANY);servAdr.sin_port = htons(atoi(argv[1]));if(bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){ErrorHandling("bind() error");}if(listen(hServSock, 5) == SOCKET_ERROR){ErrorHandling("listen() error");}clntAdrSize = sizeof(clntAdr);for(int i = 0; i < 5; i++){opndCnt = 0;hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSize);recv(hClntSock, (char*)&opndCnt, 1, 0); // recv one byterecvLen = 0;while((opndCnt * OPSZ + 1) > recvLen){recvCnt = recv(hClntSock, &opinfo[recvLen], BUF_SIZE - 1, 0);recvLen += recvCnt;}result = calculate(opndCnt, (int*)opinfo, opinfo[recvLen - 1]);send(hClntSock, (char*)&result, sizeof(result), 0);closesocket(hClntSock);}closesocket(hServSock);WSACleanup();return 0;
}
客户端:
// gcc op_client_win.c -o op_client_win -lwsock32
// op_client_win 127.0.0.1 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4void ErrorHandling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}int main(int argc, char* argv[]){WSADATA wsaData;SOCKET hSocket;char opmsg[BUF_SIZE];int result, opndCnt, i;SOCKADDR_IN servAdr;if(argc != 3){printf("Usage : %s <IP> <port>\n", argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){ErrorHandling("WSAStartup() error!");}hSocket = socket(PF_INET, SOCK_STREAM, 0);if(hSocket == INVALID_SOCKET){ErrorHandling("socket() error!");}memset(&servAdr, 0, sizeof(servAdr));servAdr.sin_family = AF_INET;servAdr.sin_addr.s_addr = inet_addr(argv[1]);servAdr.sin_port = htons(atoi(argv[2]));if(connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){ErrorHandling("connect() error!");}else{puts("Connected........");}fputs("Operand count: ", stdout);scanf("%d", &opndCnt);opmsg[0] = (char)opndCnt; // one bytefor(i = 0; i < opndCnt; i++){printf("Operand %d: ", i + 1);scanf("%d", (int*)&opmsg[i*OPSZ + 1]);}fgetc(stdin);fputs("Operator: ", stdout);scanf("%c", &opmsg[opndCnt * OPSZ + 1]); // one bytesend(hSocket, opmsg, opndCnt * OPSZ + 2, 0);recv(hSocket, (char*)&result, RLT_SIZE, 0);printf("Operation result: %d \n", result);closesocket(hSocket);WSACleanup();return 0;
}
运行结果: