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

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

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



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


可以看到,服務器創建過程一般是:

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);


發佈了70 篇原創文章 · 獲贊 29 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章