天天看點

回射客戶-伺服器模型(1)

最近在學習socket程式設計,根據自己的學習過程及學習筆記,下面來梳理一下如何實作一個簡單的回射客戶-伺服器模型,也借此來熟悉一下socket、bind、listen、accept、connect這些函數的使用。

簡單的回射客戶/伺服器模型

回射客戶-伺服器模型(1)

下面先看一下一個客戶/伺服器模型的架構圖。

回射客戶-伺服器模型(1)

可以看到,伺服器建立過程一般是:

1)建立套接字,使用socket函數,這個時候的套接字是主動套接字;

2)初始化伺服器端位址,并使用bind函數将套接口與該位址進行綁定;

3)監聽,使用listen函數,将套接口從close狀态轉為監聽狀态才能夠接收用戶端發起的連接配接,同時經過監聽之後,伺服器端的套接字從主動狀态(發起連接配接)變為被動狀态(接收連接配接);

4)等待接收連接配接,accept函數,使用該函數後,伺服器端一直處于阻塞狀态,直到用戶端的連接配接到達;

5)等用戶端的連接配接到達後,雙方開始進行通信(讀寫資料);

用戶端建立的過程就相對簡單了:

1)建立套接字;

2)使用connect函數發起連接配接(過程中要經過三次握手);

3)連接配接成功,雙方互相進行通信;

下面以代碼的方式具體說明每一步驟的實作。

伺服器端:

// 1. 建立套接字
int listenfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	perror("socket create error");

// 2. 初始化位址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;//位址族
servaddr.sin_port = htons(5188); //端口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);// 表示本機的任意位址,當然也可以自己指定
//servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 3. 綁定bind
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
	perror("bind error");

// 4. 監聽listen
if(listen(listenfd, SOMAXCONN) < 0)//第二個參數表示每一個端口的最大監聽隊列長度
	perror("listen error");
	
// 5. 接收連接配接
// 在接收連接配接前,先定義一個位址結構,用來儲存對等方的位址
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
memset(&peeraddr, 0, peerlen);
int conn; 
// 成功傳回對等方的套接口
if((conn = accept(lisenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
	perror("accept error");

//輸出用戶端的位址和端口(僅測試用)
printf("IP=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

// 至此,雙方連接配接已完成,可以進行通信
char recvbuf[1024];
while(1)
{
	memset(recvbuf, 0, sizeof(recvbuf));
	int ret = read(conn, recvbuf, sizeof(recvbuf));
	fputs(recvbuf, stdout);
	//再回射給用戶端
	write(conn, recvbuf, ret);
}

//關閉套接口
close(lisenfd);
close(conn);
           

需要對上述的一些步驟做一些說明:

1)在初始化位址的時候,伺服器端位址可以指定為具體某一IP,也可以是INADDR_ANY。這裡建議選用INADDR_ANY參數,表示位址0.0.0.0,也就是不确定的位址,或“所有位址”、“任意位址”。如果你的伺服器有多個網卡,你要在5188這個端口上監聽,所有發送到伺服器的這個端口,不管是哪個網卡/哪個IP位址接收到的資料,都可以進行處理。

2)關于listen函數中的第二個參數。每一個處于監聽狀态的端口,都要自己的監聽隊列,那麼該參數就指定了監聽隊列的長度。我們可以自己指定一個正整數,也可以使用參數SOMANCONN,它表示每個端口的最大監聽隊列長度。

用戶端:

// 1. 建立套接字
int sock;
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	perror("socket create error");
// 2. 初始化一個你要連接配接的對方的位址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 3. 發起連接配接
if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
	perror("connect error");

// 4. 進行通信(讀寫資料)
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};

while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
	//發送資料(請求連接配接)
	write(sock, sendbuf, strlen(sendbuf));
	//讀取伺服器端回射來的資料
	read(sock, recvbuf, sizeof(recvbuf));
	fputs(recvbuf, stdout);
	//每次讀取完畢需要清空緩沖區
	memset(sendbuf, 0, sizeof(sendbuf));
	memset(recvbuf, 0, sizeof(recvbuf));
}
//關閉套接口
close(sock);
           

繼續閱讀