實驗平台
linux
實驗內容
編寫TCP服務器和客戶端程序,程序運行時服務器等待客戶端連接。一旦連接成功,服務器顯示客戶端的IP地址和端口號,並向客戶端發送字符串
實驗原理
TCP是面向連接的通信,其主要實現過程如下:
我們將服務器代碼分為兩部分。
1. init_tcp_server() tcp服務器的初始化
2. main() 實現讀寫數據
這樣做的好處是main函數不必寫的特別冗長,利於維護。從框架上來說,服務器的初始化也與讀、寫無關。
tcp服務器的初始化----init_tcp_server()
1. 創建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0); //AF_INT:ipv4, SOCK_STREAM:tcp協議
2. 設置socket 當然這一步可以省略
int32_t opt = 1; ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
詳細說明:
3. 綁定(bind函數)
將socket和地址(包括ip,port)綁定。需要定義一個結構體地址,以便於將port的主機字節序轉化成網絡字節序
struct sockaddr_in serveraddr; //地址結構體
bind函數
bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))
4. listen監聽,將接收到的客戶端放入隊列
listen(sockfd,10) //第二個參數是隊列長度
5. 調用accept函數,從隊列獲取請求,返回socket描述符,如果沒有請求(沒有客戶端連接),將會阻塞,直到獲取請求
int fd=accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
至此服務器初始化完成,返回成功連接的套接字fd。
服務器端代碼如下:tcpserver.c
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #include <sys/types.h> #define PORT 1234 #define BACKLOG 10 #define BUFFER_SIZE 100 /** * @brief 初始化tcp服務器 * @param[in] listenfd 監聽套接字 * @return -1 - 失敗, socket 文件句柄 - 成功 */ int32_t init_tcp_server(int32_t listenfd) { struct sockaddr_in server; struct sockaddr_in client; int32_t connectfd = 0; int32_t addrlen; int32_t ret = 0; addrlen = sizeof(client); /**< 創建一個tcp套接字 */ listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) { perror("create socket failed!\n"); exit(1); } /**< 設置一個tcp套接字 */ int32_t opt = 1; ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (ret < 0) { perror("set socket failed!\n"); exit(1); } /**< 設置服務器監聽所有的IP地址 */ bzero(&server, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons(PORT); /**< 主機字節序轉化成網絡字節序 */ server.sin_addr.s_addr = htonl(INADDR_ANY); /**< 與服務器進行綁定 */ if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) { perror("bind error"); exit(1); } /**< 監聽 */ if (listen(listenfd, BACKLOG) == -1) { perror("listen error"); exit(1); } /**< 等待客戶端連接,如果沒有,一直阻塞 */ if ((connectfd = accept(listenfd, (struct sockaddr *)&client, &addrlen)) == -1) { perror("accept error"); close(listenfd); close(connectfd); exit(1); } printf("You got a connection from client's ip is %s, port is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port)); return connectfd; } int main() { int32_t listenfd = 0; int32_t connectfd = 0; char buf[BUFFER_SIZE] = "Welcome to my server"; connectfd = init_tcp_server(listenfd); send(connectfd, buf, BUFFER_SIZE, 0); /**< 發送信息到客戶端 */ close(connectfd); close(listenfd); }
客戶端
同樣,將客戶端代碼分成兩部分:
1. init_tcp_client() tcp客戶端的初始化
2. main() 實現讀寫數據
客戶端的初始化較為簡單,如上圖,只要實現socket和connect函數即可。但是我們希望可以手動輸入客戶端連接的IP地址,便於以后擴展,因此需要給客戶端初始化傳入一個參數。例如,輸入:
./tcpclient 127.0.0.1
客戶端代碼如下:tcpclient.c
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #include <sys/types.h> #define PORT 1234 #define BUFFER_SIZE 100 int32_t init_tcp_client(char *ipaddr) { int sockfd = 0; struct sockaddr_in server; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("create socket failed!\n"); exit(1); } bzero(&server, sizeof(struct sockaddr_in)); server.sin_family = AF_INET; server.sin_port = htons(PORT); inet_pton(AF_INET, ipaddr, &server.sin_addr.s_addr); /**< 點分十進制轉換成二進制的網絡字節序 */ if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1) { perror("connect error"); exit(1); } return sockfd; } int32_t main(int argc, char*argv[]) { int32_t sockfd, num; char buf[BUFFER_SIZE]; if (argc != 2) { printf("Usage:%s <IP Address>\n",argv[0]); exit(1); } sockfd = init_tcp_client(argv[1]); if ((num = recv(sockfd, buf, BUFFER_SIZE, 0)) == -1) { perror("recv error"); exit(1); } buf[num - 1] = '\0'; printf("Server Message: %s\n", buf); close(sockfd); return 0; }
Makefile文件如下:
all:server client server:tcpserver.c gcc tcpserver.c -o server client:tcpclient.c gcc tcpclient.c -o client clean: rm -rf server client
實驗結果如下: