一、什麽是socket?
Socket的英文原義是“孔”或“插座”。在編程中,Socket被稱做套接字,是網絡通信中的一種約定。Socket編程的應用無處不在,我們平時用的QQ、微信、浏覽器等程序,都與Socket編程有關。我們平時使用浏覽器查資料,這個過程的技術原理是怎樣的呢?
這裏的SYN和ACK是都是標志位,其中SYN代表新建一個連接,ACK代表確認。其中m、n都是隨機數。具體說明如:
- 第一次握手:SYN標志位被置位,客戶端向服務端發送一個隨機數m。
- 第二次握手:ACK、SYN標志位被置位。服務端向客戶端發送m+1表示確認剛才收到的數據,同時向客戶端發送一個隨機數n。
- 第三次握手:ACK標志被置位。客戶端向服務端發送n+1表示確認收到數據。
TCP斷開(四次揮手)
TCP斷開連接時,客戶端、服務端要完成四次數據交互工作才能建立連接,常稱爲四次揮手。可形象比喻爲如下對話:
客戶端:服務端您好,我發送數據完畢了,即將和您斷開連接。
服務端:客戶端您好,我稍稍准備一下,再給您斷開
服務端:客戶端您好,我准備好了,您可以斷開連接了。
客戶端:好的,合作愉快!
具體示意圖爲:
(2)UDP通信socket編程過程
(2)Linux的程序:
在linux下,“一切都是文件”,所以這裏我們的套接字也當做文件來看待。
服務端程序linux_tcp_server.c:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h>
#define BUF_LEN 100
int main(void) { int ServerFd, ClientFd; char Buf[BUF_LEN] = {0}; struct sockaddr ClientAddr; int addr_len = 0, recv_len = 0; struct sockaddr_in ServerSockAddr; int optval = 1; /* 創建服務端文件描述符 */ if (-1 == (ServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) { printf(“socket error!\n”); exit(1); } /* 設置服務端信息 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 給結構體ServerSockAddr清零 ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址 ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自動獲取IP地址 ServerSockAddr.sin_port = htons(6666); // 端口
// 設置地址和端口號可以重複使用 if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { printf(“setsockopt error!\n”); exit(1); } /* 綁定操作,綁定前加上上面的socket屬性可重複使用地址 */ if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(struct sockaddr))) { printf(“bind error!\n”); exit(1); } /* 進入監聽狀態 */ if (-1 == (listen(ServerFd, 10))) { printf(“listen error!\n”); exit(1); } addr_len = sizeof(struct sockaddr);
while (1) { /* 監聽客戶端請求,accept函數返回一個新的套接字,發送和接收都是用這個套接字 */ if (-1 == (ClientFd = accept(ServerFd, (struct sockaddr*)&ClientAddr, &addr_len))) { printf(“accept error!\n”); exit(1); }
/* 接受客戶端的返回數據 */ if ((recv_len = recv(ClientFd, Buf, BUF_LEN, 0)) < 0) { printf(“recv error!\n”); exit(1); } printf(“客戶端發送過來的數據爲:%s\n”, Buf); /* 發送數據到客戶端 */ send(ClientFd, Buf, recv_len, 0); /* 關閉客戶端套接字 */ close(ClientFd); /* 清空緩沖區 */ memset(Buf, 0, BUF_LEN); }
return 0; }
客戶端程序linux_tcp_client.c:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>
#define BUF_LEN 100
int main(void) { int ClientFd; char Buf[BUF_LEN] = {0}; struct sockaddr_in ServerSockAddr; /* 向服務器發起請求 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); ServerSockAddr.sin_family = AF_INET; ServerSockAddr.sin_addr.s_addr = inet_addr(“127.0.0.1”); ServerSockAddr.sin_port = htons(6666); while (1) { /* 創建客戶端socket */ if (-1 == (ClientFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) { printf(“socket error!\n”); exit(1); } /* 連接 */ if (-1 == connect(ClientFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr))) { printf(“connect error!\n”); exit(1); } printf(“請輸入一個字符串,發送給服務端:”); gets(Buf); /* 發送數據到服務端 */ send(ClientFd, Buf, strlen(Buf), 0); memset(Buf, 0, BUF_LEN); // 重置緩沖區 /* 接受服務端的返回數據 */ recv(ClientFd, Buf, BUF_LEN, 0); printf(“服務端發送過來的數據爲:%s\n”, Buf); memset(Buf, 0, BUF_LEN); // 重置緩沖區 close(ClientFd); // 關閉套接字 } return 0; }
Linux下編譯就不需要添加-lwsock32參數:
gcc linux_tcp_server.c -o linux_tcp_server gcc linux_tcp_client.c -o linux_tcp_client
實驗現象:
經上網查詢發現是端口重複使用,可以在調用bind()函數之前調用setsockopt()函數以解決端口重複使用的問題: