網(wǎng)站建設(shè)三要素寧波seo教學(xué)
端口號快速復(fù)用函數(shù)
通過
getsockopt
和setsockopt
函數(shù),管理套接字的端口號復(fù)用設(shè)置。具體操作如下:
getsockopt
函數(shù)
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
功能:獲取套接字的某些選項(xiàng)的屬性。
參數(shù):
sockfd
: 套接字描述符。level
: 獲取的層級(例如SOL_SOCKET
)。optname
: 要獲取的操作名稱(如SO_REUSEADDR
)。optval
: 獲取的值(0表示禁用,非0表示啟用)。optlen
: 參數(shù)4的大小。
返回值: 成功返回0,失敗返回-1。
setsockopt
函數(shù)
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
功能:設(shè)置套接字的某些選項(xiàng)的屬性。
參數(shù):
sockfd
: 套接字描述符。level
: 設(shè)置的層級(例如SOL_SOCKET
)。optname
: 要設(shè)置的操作名稱(如SO_REUSEADDR
)。optval
: 設(shè)置的值(0表示禁用,非0表示啟用)。optlen
: 參數(shù)4的大小。
返回值: 成功返回0,失敗返回-1。
示例代碼:端口號快速復(fù)用
// 獲取當(dāng)前端口號是否能快速復(fù)用 int n; int len = sizeof(n); getsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, &len); if (n == 0) {printf("端口號快速復(fù)用未啟動\n"); } else {printf("端口號快速復(fù)用已經(jīng)啟動\n"); }// 設(shè)置當(dāng)前套接字端口號快速復(fù)用 n = 999; setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
1. 循環(huán)服務(wù)器模型
描述
循環(huán)服務(wù)器模型是一次只處理一個客戶端請求的傳統(tǒng)方式,處理完一個客戶端請求后,才會繼續(xù)處理下一個客戶端請求。由于其效率較低,每次只能執(zhí)行單個任務(wù),因此在高并發(fā)場景下表現(xiàn)不佳。
在循環(huán)服務(wù)器模型中,服務(wù)器端采用了一個循環(huán)來不斷接收客戶端請求。每當(dāng)一個客戶端連接成功后,服務(wù)器創(chuàng)建一個新的套接字用于通信,并處理客戶端的請求。每處理完一個客戶端請求后,服務(wù)器繼續(xù)等待下一個客戶端的連接。
主要步驟:
- 創(chuàng)建套接字:使用
socket()
函數(shù)創(chuàng)建套接字。- 綁定:將套接字與服務(wù)器的 IP 地址和端口號綁定。
- 監(jiān)聽:開始監(jiān)聽客戶端連接請求。
- 循環(huán)接收客戶端請求:
- 每次
accept()
成功后,創(chuàng)建一個新的套接字用于通信。- 循環(huán)收發(fā)信息,直到客戶端斷開連接。
- 關(guān)閉連接:每次客戶端斷開連接后,關(guān)閉相應(yīng)的套接字。
關(guān)閉監(jiān)聽套接字
循環(huán)服務(wù)器代碼實(shí)現(xiàn)
代碼示例
#include <myhead.h>
#define IP "192.168.60.45"
#define PORT 6666
#define BACKLOG 20int main(int argc, const char *argv[]) {// 1. 創(chuàng)建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP協(xié)議if (oldfd == -1) {perror("socket");return -1;}// 2. 獲取端口號屬性,查看是否啟用端口號快速復(fù)用int n;int len = sizeof(n);if (getsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, &len) == -1) {perror("getsockopt");return -1;}if (n) {printf("端口號快速復(fù)用已經(jīng)啟動\n");} else {printf("端口號快速復(fù)用未啟動\n");}// 設(shè)置端口號快速復(fù)用n = 999;if (setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) {perror("setsockopt");return -1;}printf("端口號快速復(fù)用成功\n");// 3. 綁定IP和端口號struct sockaddr_in server = {.sin_family = AF_INET, // IPV4.sin_port = htons(PORT), // 端口號轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序.sin_addr.s_addr = inet_addr(IP), // IP地址};if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {perror("bind");return -1;}// 4. 監(jiān)聽if (listen(oldfd, BACKLOG) == -1) {perror("listen");return -1;}// 5. 接受客戶端連接請求并創(chuàng)建新的描述符用于通信struct sockaddr_in client;socklen_t client_len = sizeof(client);int newfd;while (1) {newfd = accept(oldfd, (struct sockaddr *)&client, &client_len);if (newfd == -1) {perror("accept");return -1;}printf("%s發(fā)來連接請求\n", inet_ntoa(client.sin_addr));// 6. 循環(huán)收發(fā)信息char buff[1024];while (1) {memset(buff, 0, sizeof(buff));int len = recv(newfd, buff, sizeof(buff), 0); // 接收客戶端信息if (len == 0) { // 客戶端斷開連接printf("客戶端下線\n");break;}printf("%s\n", buff);strcat(buff, "5201314"); // 添加回復(fù)信息send(newfd, buff, sizeof(buff), 0); // 發(fā)送信息}}// 關(guān)閉套接字close(oldfd);close(newfd);return 0;
}
代碼流程:
- 創(chuàng)建套接字:
socket()
函數(shù)創(chuàng)建一個 TCP 套接字。 - 設(shè)置端口號復(fù)用:通過
setsockopt()
設(shè)置端口復(fù)用,允許在同一端口上建立多個連接。 - 綁定:使用
bind()
將套接字與 IP 地址和端口綁定。 - 監(jiān)聽:調(diào)用
listen()
使套接字進(jìn)入監(jiān)聽狀態(tài),準(zhǔn)備接受客戶端連接。 - 接受連接:通過
accept()
接受客戶端連接,返回一個新的套接字描述符用于與客戶端通信。 - 收發(fā)消息:在循環(huán)內(nèi)通過
recv()
接收客戶端消息,然后用send()
發(fā)送回應(yīng)消息。直到客戶端斷開連接。 - 關(guān)閉套接字:處理完成后關(guān)閉套接字。
缺點(diǎn):
- 同步阻塞:當(dāng)前的設(shè)計(jì)是同步阻塞的,服務(wù)器在處理一個客戶端請求時不能同時處理其他客戶端的請求。因此,如果有多個客戶端連接,必須等待前一個客戶端的請求處理完成后才能繼續(xù)。
- 客戶端阻塞:新客戶端要通信時,必須等到舊客戶端退出。服務(wù)器只能在當(dāng)前連接斷開后接受新的客戶端請求。
- 新客戶端要通信時,必須等待舊客戶端退出。
改進(jìn):
為了解決這個問題,可以采用 多線程 或 多進(jìn)程 模型來并發(fā)處理客戶端請求,從而避免客戶端之間的阻塞。
2. 基于TCP的并發(fā)服務(wù)器
2.1 多進(jìn)程并發(fā)服務(wù)器
多進(jìn)程并發(fā)服務(wù)器的模型通常由一個父進(jìn)程負(fù)責(zé)監(jiān)聽客戶端的連接請求,每當(dāng)接收到一個連接請求時,父進(jìn)程創(chuàng)建一個新的子進(jìn)程來處理與客戶端的通信。父進(jìn)程和子進(jìn)程之間通過文件描述符傳遞數(shù)據(jù)。父進(jìn)程負(fù)責(zé)監(jiān)聽和接收客戶端連接,子進(jìn)程則處理數(shù)據(jù)收發(fā)。
多進(jìn)程服務(wù)器模型的關(guān)鍵問題:
1. 子進(jìn)程在哪創(chuàng)建: 父進(jìn)程在接收到客戶端的連接請求時會創(chuàng)建一個子進(jìn)程來處理該請求。
示例代碼:
pid_t pid = fork(); if (pid > 0) {// 父進(jìn)程:繼續(xù)監(jiān)聽新的連接請求,關(guān)閉新文件描述符close(newfd); } else if (pid == 0) {// 子進(jìn)程:關(guān)閉舊的監(jiān)聽套接字,并處理客戶端通信close(server_fd);// 與客戶端通信...close(newfd); // 處理完成后關(guān)閉與客戶端的連接exit(0); // 子進(jìn)程處理完請求后退出 } else {perror("fork");return -1; }
2. 子進(jìn)程怎么回收:子進(jìn)程處理完任務(wù)后會退出,因此需要確保父進(jìn)程能夠回收已經(jīng)退出的子進(jìn)程,防止僵尸進(jìn)程的產(chǎn)生。
常見的方式:
- 阻塞回收:父進(jìn)程使用
wait()
或waitpid()
函數(shù)在子進(jìn)程退出時阻塞等待并回收它們。- 非阻塞回收:如果不想讓父進(jìn)程被
wait()
阻塞,可以使用信號處理機(jī)制,通過捕捉SIGCHLD
信號并在信號處理函數(shù)中回收子進(jìn)程。示例代碼(非阻塞回收):
// 信號處理函數(shù),用于回收僵尸進(jìn)程 void handle_sigchld(int sig) {while (waitpid(-1, NULL, WNOHANG) > 0); // 回收已退出的子進(jìn)程 }// 在主程序中捕捉 SIGCHLD 信號 signal(SIGCHLD, handle_sigchld);
3. 文件描述符: 由于系統(tǒng)對打開的文件描述符有限制,因此需要確保在父子進(jìn)程中正確管理文件描述符。
在操作系統(tǒng)中,文件描述符是有限的,每個進(jìn)程可以打開的文件(包括套接字)的數(shù)量是有限的。如果服務(wù)器并發(fā)連接數(shù)較多,且每個連接都創(chuàng)建一個新的子進(jìn)程,就可能遇到文件描述符耗盡的問題??梢酝ㄟ^以下方式解決或避免這一問題:
解決方案:
- 增加文件描述符限制:可以通過
ulimit -n
命令臨時增加系統(tǒng)的文件描述符限制,或者在/etc/security/limits.conf
文件中永久增加限制。- 使用線程池或連接池:而不是為每個客戶端創(chuàng)建一個新的子進(jìn)程,可以通過線程池或連接池來管理客戶端連接。多線程或線程池模型可以減少系統(tǒng)開銷,因?yàn)榫€程的創(chuàng)建和銷毀比進(jìn)程更加輕量。
- 復(fù)用文件描述符:通過一些技術(shù)手段(例如
select()
、poll()
或epoll()
)來管理多個連接,不需要為每個客戶端創(chuàng)建一個獨(dú)立的進(jìn)程或線程,從而避免耗盡文件描述符。
示例:修改文件描述符限制
- 臨時修改文件描述符的限制:
ulimit -n 65535
- 永久修改文件描述符的限制: 在
/etc/security/limits.conf
文件中添加:?* soft nofile 65535 * hard nofile 65535
總結(jié)
- 子進(jìn)程創(chuàng)建:父進(jìn)程在接收到客戶端連接請求后,使用
fork()
創(chuàng)建子進(jìn)程來處理該客戶端的通信。- 子進(jìn)程回收:父進(jìn)程可以通過
wait()
或waitpid()
回收子進(jìn)程,也可以通過信號處理函數(shù)來非阻塞回收已退出的子進(jìn)程。- 文件描述符限制:多進(jìn)程模型可能會受到系統(tǒng)文件描述符限制的影響,解決方法包括增加文件描述符限制、使用線程池或連接池等技術(shù)來減少文件描述符的占用。
多進(jìn)程并發(fā)服務(wù)器執(zhí)行模型:
定義信號處理函數(shù),非阻塞回收僵尸進(jìn)程。
綁定子進(jìn)程退出時的信號。
- 創(chuàng)建套接字
- 綁定
- 監(jiān)聽
- 循環(huán)接收客戶端連接
- 讓父進(jìn)程接收客戶端請求并關(guān)閉新文件描述符,子進(jìn)程關(guān)閉舊的描述符只負(fù)責(zé)數(shù)據(jù)收發(fā)。
多進(jìn)程服務(wù)器代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <wait.h>#define IP "192.168.60.45" #define PORT 6666 #define BACKLOG 100// 信號處理函數(shù),用于回收僵尸進(jìn)程 void fun(int sss) {if (sss == SIGCHLD){while (waitpid(-1, NULL, 0) > 0); // 循環(huán)回收子進(jìn)程} }int main(int argc, const char *argv[]) {// 1. 捕獲子進(jìn)程退出時的信號if (signal(SIGCHLD, fun) == SIG_ERR){perror("signal");return -1;}// 2. 創(chuàng)建TCP類型的套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1){perror("socket");return -1;}// 3. 設(shè)置端口號快速復(fù)用int n = 1;if (setsockopt(oldfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1){perror("setsockopt");return -1;}printf("端口號快速復(fù)用成功\n");// 4. 綁定struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("bind");return -1;}// 5. 監(jiān)聽if (listen(oldfd, BACKLOG) == -1){perror("listen");return -1;}struct sockaddr_in client;socklen_t client_len = sizeof(client);char buff[1024];// 6. 主循環(huán)接收客戶端連接while (1){int newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); // 接收客戶端連接printf("%s發(fā)來連接請求\n", inet_ntoa(client.sin_addr));pid_t pid = fork(); // 創(chuàng)建子進(jìn)程if (pid > 0) // 父進(jìn)程監(jiān)聽,并關(guān)閉新的描述符{close(newfd);}else if (pid == 0) // 子進(jìn)程處理數(shù)據(jù)收發(fā){close(oldfd); // 子進(jìn)程關(guān)閉舊的描述符while (1){int len = recv(newfd, buff, sizeof(buff), 0);if (len == 0){printf("客戶端退出\n");break;}printf("客戶端%s發(fā)來消息:%s\n", inet_ntoa(client.sin_addr), buff);strcat(buff, inet_ntoa(client.sin_addr)); // 回去時加上客戶端IPsend(newfd, buff, sizeof(buff), 0);}close(newfd);exit(0); // 子進(jìn)程退出}else{perror("fork");return -1;}}return 0; }
客戶端代碼:
客戶端通過連接到服務(wù)器來發(fā)送數(shù)據(jù)并接收服務(wù)器的響應(yīng)。該程序創(chuàng)建一個TCP套接字,連接到指定的服務(wù)器IP和端口,并與服務(wù)器進(jìn)行數(shù)據(jù)交互。
#include <myhead.h>#define IP "192.168.60.45" #define PORT 6666int main(int argc, const char *argv[]) {// 1. 創(chuàng)建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1){perror("socket");return -1;}// 2. 連接服務(wù)器struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("connect");return -1;}// 3. 數(shù)據(jù)收發(fā)char buff[1024];while (1){fgets(buff, sizeof(buff), stdin); // 鍵盤輸入字符串buff[strlen(buff) - 1] = '\0'; // 去除換行符// 發(fā)送數(shù)據(jù)到服務(wù)器send(oldfd, buff, sizeof(buff), 0);// 接收服務(wù)器數(shù)據(jù)int len = recv(oldfd, buff, sizeof(buff), 0);if (len == 0){printf("服務(wù)器意外退出\n");break;}printf("接收服務(wù)器消息:%s\n", buff);}// 關(guān)閉套接字close(oldfd);return 0; }
?總結(jié)
- 多進(jìn)程并發(fā)模型: 通過父進(jìn)程監(jiān)聽和創(chuàng)建子進(jìn)程來處理每個客戶端的請求,使用
fork
創(chuàng)建子進(jìn)程,在子進(jìn)程中負(fù)責(zé)數(shù)據(jù)收發(fā),父進(jìn)程關(guān)閉新文件描述符,子進(jìn)程關(guān)閉舊文件描述符。通過信號處理函數(shù)回收子進(jìn)程,避免僵尸進(jìn)程。- TCP客戶端: 連接到服務(wù)器后,通過套接字發(fā)送數(shù)據(jù)并接收響應(yīng),支持實(shí)時數(shù)據(jù)交互。
這種模型適用于客戶端和服務(wù)器之間的實(shí)時通信,特別是在需要處理多個客戶端請求的場景中。
2.2 多線程并發(fā)服務(wù)器
1. 多線程的優(yōu)勢
- 資源開銷小:線程的資源開銷比進(jìn)程小,創(chuàng)建和銷毀線程的速度比進(jìn)程快。
- 響應(yīng)速度快:如果客戶端連接較多,可以通過線程池提前創(chuàng)建線程來分配給客戶端,減少響應(yīng)延遲。
- 線程銷毀開銷小:線程占用資源較少,銷毀線程的資源開銷也較小。
2. 服務(wù)器模型
- 主線程:負(fù)責(zé)監(jiān)聽客戶端連接。
- 子線程:負(fù)責(zé)接收和發(fā)送數(shù)據(jù),與客戶端通信。
3. 服務(wù)器的工作流程
- 創(chuàng)建原始套接字:使用
socket()
創(chuàng)建一個 TCP 類型的套接字。- 綁定 IP 地址和端口號:使用
bind()
綁定服務(wù)器的 IP 地址和端口。- 監(jiān)聽客戶端連接:使用
listen()
開始監(jiān)聽客戶端的連接請求。- 接收客戶端連接:使用
accept()
接收客戶端的連接請求,并為每個客戶端創(chuàng)建一個新線程。- 線程處理客戶端請求:每個線程接收到客戶端消息后進(jìn)行處理并發(fā)送響應(yīng)。線程執(zhí)行完后退出。
4. 線程池
- 為了處理大量并發(fā)請求,線程池的機(jī)制可以預(yù)先創(chuàng)建一定數(shù)量的線程,接收到客戶端請求時,從線程池中取出一個線程來處理該請求。
5. 代碼實(shí)現(xiàn)
#include <myhead.h>// 定義服務(wù)器的 IP 地址、端口號和最大連接數(shù) #define IP "192.168.60.45" #define PORT 9999 #define BACKLOG 10// 定義結(jié)構(gòu)體,用于傳遞客戶端信息和新文件描述符 typedef struct {struct sockaddr_in client; // 客戶端信息int newfd; // 新的文件描述符 } ZYJ;// 線程體函數(shù),處理與客戶端的通信 void *fun(void *sss) {// 獲取新文件描述符和客戶端信息int newfd = ((ZYJ *)sss)->newfd;struct sockaddr_in client = ((ZYJ *)sss)->client;// 打印客戶端的 IP 地址printf("%s發(fā)來信息\n", inet_ntoa(client.sin_addr));char buff[1024];while (1) {// 接收客戶端發(fā)來的消息int len = recv(newfd, buff, sizeof(buff), 0);if (len == 0) { // 客戶端退出printf("客戶端退出\n");break;}// 打印收到的消息printf("收到消息:%s\n", buff);// 在消息末尾加上 "1973"strcat(buff, "1973");// 將處理后的消息發(fā)送回客戶端send(newfd, buff, sizeof(buff), 0);}// 線程結(jié)束時退出pthread_exit(NULL); }int main(int argc, const char *argv[]) {// 1. 創(chuàng)建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1) {perror("socket");return -1;}// 2. 綁定 IP 地址和端口號struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) {perror("bind");return -1;}// 3. 監(jiān)聽客戶端的連接請求if (listen(oldfd, BACKLOG) == -1) {perror("listen");return -1;}struct sockaddr_in client;socklen_t client_len = sizeof(client);// 4. 主線程循環(huán)接收客戶端連接while (1) {int newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); // 接收客戶端連接請求ZYJ sb;sb.newfd = newfd; // 保存新的文件描述符sb.client = client; // 保存客戶端信息pthread_t tid;// 5. 創(chuàng)建子線程處理客戶端請求tid = pthread_create(&tid, NULL, fun, &sb);if (tid == -1) {perror("pthread_create");return -1;}// 6. 將線程設(shè)為分離狀態(tài),系統(tǒng)會自動回收線程資源pthread_detach(tid);}return 0; }
代碼功能
- 創(chuàng)建套接字:通過
socket()
創(chuàng)建一個 TCP 套接字。- 綁定 IP 地址和端口:通過
bind()
將套接字綁定到指定的 IP 地址和端口號。- 監(jiān)聽連接:調(diào)用
listen()
開始監(jiān)聽來自客戶端的連接請求,最多允許BACKLOG
個連接排隊(duì)。- 接受連接:主線程通過
accept()
接收客戶端的連接請求,并返回一個新的文件描述符newfd
用于與客戶端進(jìn)行通信。- 創(chuàng)建子線程:每當(dāng)接受到新的連接,主線程會通過
pthread_create()
創(chuàng)建一個子線程處理客戶端的消息傳遞和接收。每個子線程的處理邏輯通過fun()
函數(shù)完成。- 線程回收:通過
pthread_detach()
將線程設(shè)置為分離狀態(tài),子線程在完成后會自動回收資源,避免主線程等待子線程退出。
關(guān)鍵函數(shù)
socket()
:創(chuàng)建一個套接字。bind()
:將套接字與本地的 IP 地址和端口號綁定。listen()
:讓套接字進(jìn)入監(jiān)聽狀態(tài),準(zhǔn)備接受客戶端連接。accept()
:阻塞等待客戶端連接,并返回一個新的套接字用于與客戶端通信。pthread_create()
:創(chuàng)建新的線程來處理客戶端請求。pthread_detach()
:將線程設(shè)為分離狀態(tài),線程結(jié)束后自動回收資源。recv()
:接收客戶端發(fā)送的消息。send()
:向客戶端發(fā)送消息。
適用場景
這個多線程模型適用于客戶端連接量較大或較為頻繁的場景。每個客戶端連接都會分配一個線程進(jìn)行處理,因此可以在短時間內(nèi)處理多個客戶端的請求。
線程池優(yōu)化
雖然該程序?yàn)槊總€連接創(chuàng)建了一個獨(dú)立的線程,但在實(shí)際應(yīng)用中,為了提高資源利用率和性能,可以引入線程池。線程池事先創(chuàng)建好一組線程,客戶端請求到來時從線程池中取出一個線程來處理,而不是每次都創(chuàng)建一個新的線程。這樣可以減少線程的創(chuàng)建和銷毀開銷。
注意事項(xiàng)
- 多線程同步問題:雖然本例中沒有涉及多線程之間的共享資源,但在復(fù)雜應(yīng)用中可能需要考慮線程同步機(jī)制(如互斥鎖、條件變量等)。
- 線程回收:線程被設(shè)為分離狀態(tài)(
pthread_detach()
),這樣可以在線程結(jié)束后由系統(tǒng)自動回收資源,而不需要調(diào)用pthread_join()
。
總結(jié)
該程序展示了一個典型的多線程并發(fā)服務(wù)器模型,使用線程處理每個客戶端請求,減少了主線程的負(fù)擔(dān),提高了處理效率。通過
pthread_detach()
來自動回收資源,避免了線程泄露問題。
6.? 客戶端代碼示例
#include <myhead.h>#define IP "192.168.60.45" #define PORT 9999int main(int argc, const char *argv[]) {// 1、創(chuàng)建套接字int oldfd = socket(AF_INET, SOCK_STREAM, 0);if (oldfd == -1){perror("socket");return -1;}// 2、連接服務(wù)器struct sockaddr_in server = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = inet_addr(IP),};if (connect(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1){perror("connect");return -1;}// 3、數(shù)據(jù)收發(fā)char buff[1024];while (1){fgets(buff, sizeof(buff), stdin); // 鍵盤輸入字符串buff[strlen(buff) - 1] = '\0'; // 去除換行符// 發(fā)送數(shù)據(jù)到服務(wù)器send(oldfd, buff, sizeof(buff), 0);// 接收服務(wù)器數(shù)據(jù)int len = recv(oldfd, buff, sizeof(buff), 0);if (len == 0){printf("服務(wù)器意外退出\n");break;}printf("接收服務(wù)器消息:%s\n", buff);}// 關(guān)閉套接字close(oldfd);return 0; }