Socket編程實踐(3) 多連接服務器實現與簡單P2P聊天程序例程

SO_REUSEADDR選項

在上一篇文章的最后我們貼出了一個簡單的C/S通信的例程。在該例程序中,使用"Ctrl+c"結束通信后,服務器是無法立即重啟的,如果嘗試重啟服務器,將被告知:

bind: Address already in use

原因在于服務器重新啟動時需要綁定地址:

bind (listenfd , (struct sockaddr*)&servaddr, sizeof(servaddr));

而這個時候網絡正處于TIME_WAIT的狀態,只有在TIME_WAIT狀態退出后,套接字被刪除,該地址才能被重新綁定。TIME_WAIT的時間是兩個MSL,大約是1~4分鐘。若每次服務器重啟都需要等待TIME_WAIT結束那就太不合理了,好在選項SO_REUSEADDR能夠解決這個問題。
服務器端盡可能使用REUSEADD,在bind()之前調用setsockopt來設置SO_REUSEADDR套接字選項,使用SO_REUSEADDR選項可以使不必等待TIME_WAIT狀態消失就可以重啟服務器。


/*設置地址重復使用*/
int on = 1; //on為1表示開啟
if(setsockopt(listenfp ,SOL_SOCKET,SO_REUSEADDR,&on,sieof(on))<0)ERR_EXIT("setsockopt error");

處理多客戶的服務器

在上一篇文章例程中,服務器端只能夠連接一個客戶端,并不能處理多個客戶端的連接。原因在于服務器使用accept從已連接隊列中獲取一個連接后,便進入了對該連接的服務中,處于while循環狀態。當一個新的客戶端連接已經放入已連接隊列時,服務器并不能執行到accpet的代碼去獲取隊列中的連接。
為了解決這個問題,我們可以fork()一個子進程,讓子進程來處理一個客戶端的連接,而父進程循環執行accept的代碼,獲取新的連接:

        int conn ;while(1){conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);if(conn <0)ERR_EXIT("accept error");elseprintf("連接到服務器的客戶端的IP地址是:%s,端口號是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));pid_t pid ;pid = fork();//創建一個新進程if (pid ==0) //子進程{close(listenfd); //子進程不需要監聽套接字,將其關閉/*循環獲取數據、發送數據*/char recvbuf[1024];while(1){memset(recvbuf,0,sizeof(recvbuf));int ret = read(conn,recvbuf ,sizeof(recvbuf));fputs(recvbuf,stdout);write(conn,recvbuf,sizeof(recvbuf));}exit(EXIT_SUCCESS);}if(pid >0) //父進程{close(conn);//父進程無需該連接套接字,它的任務是執行accept獲取連接      }else {close(conn);}}

啟動服務器端,使用多個客戶端進行連接,可以看到服務器能夠同時處理多個連接:
610439-20160426155331830-1286531006.png

實現一個P2P簡單聊天程序

為了實現聊天的功能,客戶端與服務器端都需要有一個進程來讀取連接,另一個進程來處理鍵盤輸入。使用fork()來完成這個簡單的聊天程序。
客戶端程序:

//p2pcli.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\do \{\perror(m);\exit(EXIT_FAILURE);\}while(0)
void handler()
{exit(EXIT_SUCCESS);
}
int main()
{/*創建一個套接字*/int sock = socket(AF_INET,SOCK_STREAM,0);if(sock == -1){ERR_EXIT("socket");}/*定義一個地址結構*/struct sockaddr_in servaddr;memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(5888);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*進行連接*/if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){ERR_EXIT("connect");}else{printf("連接成功\n");}pid_t pid ;pid = fork();if(pid == -1)ERR_EXIT("fork");if(pid == 0) //子進程復制接收數據并顯示出來{char recvbuf[1024]={0};while(1){memset(recvbuf,0,sizeof(recvbuf));int ret = read(sock ,recvbuf,sizeof(recvbuf));if(ret == -1){ERR_EXIT("read");}if(ret == 0) //連接關閉{printf("連接關閉\n");kill(getppid(),SIGUSR1);break;}else{printf("接收到信息:");fputs(recvbuf,stdout);}}}else //父進程負責從鍵盤接收輸入并發送{signal(SIGUSR1,handler);char sendbuf[1024]={0}  ;while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL){write(sock,sendbuf,strlen(sendbuf));memset(&sendbuf,0,sizeof(sendbuf));}}close(sock);return 0;
}

服務器端程序:

// p2pser.c
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#define ERR_EXIT(m)\do \{\perror(m);\exit(EXIT_FAILURE);\}while(0)
/*信號處理函數*/
void handler(int sig)
{exit(EXIT_SUCCESS);
}
int main()
{/* 創建一個套接字*/int listenfd= socket(AF_INET ,SOCK_STREAM,0);if(listenfd==-1)ERR_EXIT("socket");/*定義一個地址結構并填充*/struct sockaddr_in addr;addr.sin_family = AF_INET;   //協議族為ipv4addr.sin_port = htons(5888); //綁定端口號addr.sin_addr.s_addr = htonl(INADDR_ANY);//主機字節序轉為網絡字節序/*重復使用地址*/int on = 1;if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0){ERR_EXIT("setsockopt");}/*將套接字綁定到地址上*/if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1){ERR_EXIT("bind");}/*監聽套接字,成為被動套接字*/if(listen(listenfd,SOMAXCONN)<0){ERR_EXIT("Listen");}struct sockaddr_in peeraddr;socklen_t peerlen = sizeof(peeraddr);int conn ;conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);if(conn <0)ERR_EXIT("accept error");elseprintf("連接到服務器的客戶端的IP地址是:%s,端口號是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));pid_t pid ;pid = fork();//創建一個新進程if(pid == -1){ERR_EXIT("fork");}if(pid == 0)//子進程{signal(SIGUSR1,handler);char sendbuf[1024] = {0};while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL){write(conn,sendbuf,sizeof(sendbuf));memset(sendbuf,0,sizeof(sendbuf));}exit(EXIT_SUCCESS);}else    //父進程 用來獲取數據{char recvbuf [1024]={0};while(1){memset(recvbuf,0,sizeof(recvbuf));int ret = read(conn ,recvbuf,sizeof(recvbuf));if(ret == -1){ERR_EXIT("read");}if(ret == 0) //對方已關閉 {printf("對方關閉\n");break;}fputs(recvbuf,stdout);}kill(pid,SIGUSR1);exit(EXIT_SUCCESS);}/*關閉套接字*/close(listenfd);close(conn);return 0;

轉載于:https://www.cnblogs.com/QG-whz/p/5435396.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458029.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458029.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458029.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

work2的code和問題

//常量 package cn.itcast.work2; public class Constant { //constant 常量   public static void main(String[] args) { /* * 字符串常量 用雙引號括起來的內容 * 整數常量 所有整數 * 小數常量 所有小數 * 字符常量 用單引號括起來的內容,里面只能放單個數字,單個字母或單…

Linux-(C/C++)生成并使用靜態庫/動態庫

靜態庫/動態庫概要 在Windows下靜態庫的后綴為&#xff1a;.lib、動態庫后綴為&#xff1a;.dll&#xff1b;而在Linux下靜態庫的后綴為&#xff1a;.a、動態庫的后綴為&#xff1a;.so。 那么什么是靜態庫呢&#xff1f; 首先我們來看看程序編譯的大體流程&#xff1a;預處理…

windows下最好的圍棋_學圍棋能使學習成績提高嗎?

孩子幾歲開始學習圍棋最合適&#xff1f;當然要4歲開始學圍棋。一是因為&#xff1a;4歲是幼兒形狀知覺形成的關鍵期&#xff0c;圍棋千變萬化的棋形(見文后圖)是最有利于促進與鍛煉孩子形狀知覺的形成。二是因為&#xff1a;人的大腦在3周歲后基本已經發育了60%&#xff0c;到…

每日站立會議05

組員一起討論軟件和關鍵功能的實現&#xff0c;在查詢相關資料、詢問老師、查詢網絡之后&#xff0c;無果。轉載于:https://www.cnblogs.com/zuhaoran/p/5435904.html

C#與C/C++的交互zz

C#與C交互&#xff0c;總體來說可以有兩種方法&#xff1a; 利用C/CLI作為代理中間層 利用PInvoke實現直接調用 第一種方法&#xff1a;實現起來比較簡單直觀&#xff0c;并且可以實現C#調用C所寫的類&#xff0c;但是問題是MONO構架不支持C/CLI功能&#xff0c;因此無法實現脫…

curl查看swift狀態命令_HTTP 請求與響應包括哪些,如何用Chrome查看 HTTP 請求與響應內容和curl 命令的使用...

1.HTTP的請求和響應其實就是通過電腦上的軟件來進行的&#xff0c;客戶端請求的內容發送到服務器上&#xff0c;服務器收到請求后就會響應客戶端的請求&#xff0c;如圖&#xff1a;HTTP請求的內容及格式&#xff1a;請求最多包含四部分&#xff0c;最少包含三部分。&#xff0…

Pytorch的BatchNorm層使用中容易出現的問題

前言 本文主要介紹在pytorch中的Batch Normalization的使用以及在其中容易出現的各種小問題&#xff0c;本來此文應該歸屬于[1]中的&#xff0c;但是考慮到此文的篇幅可能會比較大&#xff0c;因此獨立成篇&#xff0c;希望能夠幫助到各位讀者。如有謬誤&#xff0c;請聯系指出…

android 比較靠譜的圖片壓縮

2019獨角獸企業重金招聘Python工程師標準>>> 第一&#xff1a;我們先看下質量壓縮方法&#xff1a; private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, …

jetty上手

jetty簡介&#xff1a;維基百科 Jetty是一個純粹的基于Java的網頁服務器和Java Servlet容器。盡管網頁服務器通常用來為人們呈現文檔&#xff0c;但是Jetty通常在較大的軟件框架中用于計算機與計算機之間的通信。Jetty支持最新的Java Servlet API&#xff08;帶JSP的支持&#…

常用公差配合表圖_ER彈簧夾頭配套BT刀柄常用規格型號表

ER彈簧夾頭具有定心精度高&#xff0c;夾緊力均勻的特點&#xff0c;廣泛用于機械類零件的精加工和半精加工&#xff0c;通常與BT刀柄匹配使用。BT刀柄是是機械主軸與刀具和其它附件工具連接件&#xff0c;BT為日本標準(MAS403)&#xff0c;現在也是普遍使用的一種標準。傳統刀…

Spatial Transformer Networks(STN)

詳細解讀Spatial Transformer Networks&#xff08;STN&#xff09;-一篇文章讓你完全理解STN了_多元思考力-CSDN博客_stn

Linux下python安裝升級詳細步驟 | Python2 升級 Python3

Linux下python升級步驟 Python2 ->Python3 多數情況下&#xff0c;系統自動的Python版本是2.x 或者yum直接安裝的也是2.x 但是&#xff0c;現在多數情況下建議使用3.x 那么如何升級呢&#xff1f; 下面老徐詳細講解升級步驟&#xff1b; 首先下載源tar包 可利用linux自帶下…

華為手機連電腦_手機、電腦無網高速互傳!華為神技逆天

Huawei Share是華為的一項自研多終端傳輸技術&#xff0c;可以在沒有網絡狀態下實現手機與手機、電腦等多終端設備間快速穩定的文件分享&#xff0c;尤其是在辦公場景下&#xff0c;可以極大提升辦公效率。華為表示&#xff0c;未來Huawei Share將應用于更多全場景跨設備無縫分…

【無標題】移動端深度學習開源框架及部署(對比)

移動端深度學習開源框架及部署 - 凌逆戰 - 博客園

Github基本操作的學習與溫習

GitHub是最先進的分布式版本控制工具&#xff0c;下面是我學習中總結的操作流程&#xff0c;僅供參考 -----------------------------------------------------------------------------------------------------------------------------------------------------------------…

excel統計行數_值得收藏的6個Excel函數公式(有講解)

收藏的Excel函數大全公式再多&#xff0c;幾天不用也會忘記。怎么才能不忘&#xff1f;你需要了解公式的運行原理。小編今天不再推送一大堆函數公式&#xff0c;而是根據提問最多的問題&#xff0c;精選出6個實用的&#xff0c;然后詳細的解釋給大家。1、計算兩個時間差TEXT(B2…

Studio One正版多少錢 Studio One正版怎么購買

隨著版權意識的增強&#xff0c;打擊盜版的力度越來越大&#xff0c;現在網絡上的盜版資源越來越少&#xff0c;資源少很難找是一方面&#xff0c;另一方面使用盜版軟件不僅很多功能不能使用&#xff0c;而且很多盜版軟件都被植入各種木馬病毒&#xff0c;從而帶來各種各樣的風…

DNS簡述

常見DNS記錄SOA&#xff1a;域權威開始NS&#xff1a;權威域名服務器A&#xff1a;主機地址CNAME&#xff1a;別名對應的正規名稱MX&#xff1a;郵件傳遞服務器PTR&#xff1a;域名指針 (用于反向 DNS)查詢過程瀏覽器緩存->hosts->LDNS->LDNS緩存->ISP->ISP緩存…

cuda gpu相關匯總

1.Ubuntu16.04:在anaconda下安裝pytorch-gpu 轉自&#xff1a;Ubuntu16.04:在anaconda下安裝pytorch-gpu_莫等閑996的博客-CSDN博客 1 創建虛擬環境并進入 conda create -n pytorch-gpu python3.6 conda activate pytorch-gpu 2 下載對應的安裝包和配件 方法一(推薦)&#…

普通人學python有意義嗎_學python難嗎

首先&#xff0c;對于初學者來說學習Python是不錯的選擇&#xff0c;一方面Python語言的語法比較簡單易學&#xff0c;另一方面Python的實驗環境也比較容易搭建。學習Python需要的時間取決于三方面因素。(推薦學習&#xff1a;Python視頻教程)其一是學習者是否具有一定的計算機…