基于TCP通信的簡單服務(wù)端和客戶端程序

背景

因為最近在研究網(wǎng)絡(luò)相關(guān)的東西,因此經(jīng)常要寫程序做實驗來驗證。主要是TCP通信,因此就寫了個簡單的基于TCP通信的小程序,方便以后要使用的時候能直接復(fù)用,省的還要各種谷歌、百度。

功能介紹

寫的很簡單,實現(xiàn)的就是客戶端讀取鍵盤輸入,發(fā)送給服務(wù)端,服務(wù)端打印出該輸入。

因為只研究TCP通信原理,就沒有再做其他的多線程并發(fā)之類的功能。

代碼

1、先看服務(wù)端代碼

/*服務(wù)端TCP程序一般流程

*1、創(chuàng)建socket

*2、綁定端口和ip

*3、監(jiān)聽socket

*4、接收客戶端的請求

*5、從緩沖區(qū)中讀取數(shù)據(jù)

*/

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/un.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#define PORT 5000

#define IP "192.168.0.106"

#define BACKLOG 5

int main()

{

? ? int listen_fd;

? ? int accept_fd;

? ? char buf[1024] = {0};//讀寫緩沖區(qū)

? ? struct sockaddr_in server_addr;

? ? //需要獲取客戶端相關(guān)信息

? ? struct sockaddr_in client_addr;

? ? socklen_t client_len;

? ? client_len = sizeof(client_addr);

? ? //創(chuàng)建socket

? ? if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

? ? ? ? perror("socket");

? ? ? ? return 1;

? ? }

? ? //綁定端口和ip

? ? server_addr.sin_family = AF_INET;

? ? server_addr.sin_port = htons(PORT);

? ? server_addr.sin_addr.s_addr = inet_addr(IP);

? ? if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {

? ? ? ? perror("bind");

? ? ? ? return 2;

? ? }

? ? //監(jiān)聽socket

? ? if (listen(listen_fd, BACKLOG) < 0) {

? ? ? ? perror("listen");

? ? ? ? return 3;

? ? }

? ? while(1) {

? ? ? ? //接收客戶端的請求

? ? ? ? if ((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len)) < 0) {

? ? ? ? ? ? perror("accept");

? ? ? ? ? ? return 4;

? ? ? ? }

? ? ? ? //打印客戶端信息

? ? ? ? printf("connected with ip: %s: port:%d\n",

? ? ? ? ? ? inet_ntop(AF_INET, &client_addr.sin_addr, buf, 1024), ntohs(client_addr.sin_port));

? ? ? ? while (1) {

? ? ? ? ? ? memset(buf, 0, sizeof(buf));

? ? ? ? ? ? //從緩沖區(qū)中讀取數(shù)據(jù)

? ? ? ? ? ? ssize_t size = read(accept_fd, buf, sizeof(buf) - 1);

? ? ? ? ? ? if (size > 0)

? ? ? ? ? ? ? ? printf("client send: %s\n", buf);

? ? ? ? ? ? else if (size == 0) {

? ? ? ? ? ? ? ? printf("read done!\n");

? ? ? ? ? ? ? ? close(accept_fd);

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? }

? ? ? ? ? ? else {

? ? ? ? ? ? ? ? perror("read");

? ? ? ? ? ? ? ? close(accept_fd);

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? }?

? ? ? ? }

? ? }

? ? close(listen_fd);

? ? return 0;

}

2、然后是客戶端代碼

/*客戶端TCP程序一般流程

*1、創(chuàng)建socket

*2、向服務(wù)端發(fā)起連接

*3、向緩沖區(qū)中寫入數(shù)據(jù)

*/

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/un.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#define PORT 5000

#define IP "192.168.0.106"

int main()

{

? ? int client_fd;

? ? char buf[1024] = {0};//讀寫緩沖區(qū)

? ? struct sockaddr_in client_addr;

? ? ssize_t size;

? ? //創(chuàng)建socket

? ? if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

? ? ? ? perror("socket");

? ? ? ? return 1;

? ? }

? ? //填充ip端口信息

? ? client_addr.sin_family = AF_INET;

? ? client_addr.sin_port = htons(PORT);

? ? client_addr.sin_addr.s_addr = inet_addr(IP);

? ? //向服務(wù)端發(fā)起連接

? ? if (connect(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {

? ? ? ? perror("connect");

? ? ? ? return 2;

? ? }

? ? while(1) {

? ? ? ? memset(buf, 0, sizeof(buf));

? ? ? ? //從標(biāo)準(zhǔn)輸入獲取字符串

? ? ? ? if (!gets(buf)) {

? ? ? ? ? ? perror("gets");

? ? ? ? ? ? return 3;

? ? ? ? }

? ? ? ? if (strcmp(buf, "quit") == 0)

? ? ? ? ? ? break;

? ? ? ? if (write(client_fd, buf, strlen(buf)) != strlen(buf)) {

? ? ? ? ? ? perror("write");

? ? ? ? ? ? return 4;

? ? ? ? }

? ? }

? ? close(client_fd);

? ? return 0;

}

3、最后是一個簡單的Makefile

all:

? ? gcc server.c -o server

? ? gcc client.c -o client

clean:

? ? rm -rf server client

編譯后程序就能跑起來了,使用netstat命令查看連接狀態(tài)如下:

客戶端退出后,服務(wù)端還是在監(jiān)聽新連接的到來。

探討

1、對于服務(wù)端代碼的accept函數(shù)

一般情況我們是將第二、三個入?yún)⒅脼榭盏模硎疚覀儾魂P(guān)注客戶端信息,如下

accept_fd = accept(listen_fd, NULL, NULL);

此處我們?yōu)榱舜蛴】蛻舳薸p和端口,因此傳入了我們定義的結(jié)構(gòu)體,用于獲取客戶端信息。

2、關(guān)于服務(wù)端監(jiān)聽ip,或者是說綁定的ip

一般我們服務(wù)端程序監(jiān)聽全網(wǎng)段ip,即在調(diào)用bind函數(shù)時ip地址使用INADDR_ANY作為參數(shù),如下,

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

這兩種使用的區(qū)別我們可以通過netstat命令查看,對比如下,

上面的圖為設(shè)置了具體監(jiān)聽ip的程序,下面是使用INADDR_ANY為參數(shù)的程序。由此,我們編程時就需要根據(jù)環(huán)境上是否有多個ip,程序是否需要監(jiān)聽所有網(wǎng)卡的端口來確定我們函數(shù)的參數(shù)設(shè)置。

3、客戶端程序一般不調(diào)用bind函數(shù)

在客戶端程序中我們一般不調(diào)用bind函數(shù),因為我們其實一般并不關(guān)心客戶端使用什么ip和什么端口和服務(wù)端通信,內(nèi)核會替你選擇一個未被占用的端口以及能和服務(wù)端通信的ip來發(fā)起連接。但是作為客戶端,我們是否可以使用bind函數(shù)呢?答案是肯定的。考慮以下場景,如果我們想要指定客戶端連接從哪個ip出去,使用哪個端口,這時候就必須使用bind函數(shù)了,這也就是bind函數(shù)的作用。

我們將上面客戶端的程序稍微修改一下,增加bind操作。為了更好的看出bind的結(jié)果,我在環(huán)境上配了兩個ip,讓客戶端和服務(wù)端各在一個ip上進(jìn)行通信。我們配置客戶端使用5555號端口,綁定在ip:192.168.0.107上,如代碼所示:

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/un.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#define PORT 5000

#define IP "192.168.0.106"

int main()

{

? ? int client_fd;

? ? char buf[1024] = {0};//讀寫緩沖區(qū)

? ? struct sockaddr_in client_addr;

? ? ssize_t size;

? ? //創(chuàng)建socket

? ? if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

? ? ? ? perror("socket");

? ? ? ? return 1;

? ? }

? ? /**************************************************************************/

? ? //客戶端使用bind函數(shù)綁定ip和端口

? ? struct sockaddr_in client_bind;

? ? client_bind.sin_family = AF_INET;

? ? client_bind.sin_port = htons(5555);

? ? client_bind.sin_addr.s_addr = inet_addr("192.168.0.107");

? ? if (bind(client_fd, (struct sockaddr*)&client_bind, sizeof(client_bind)) < 0) {

? ? ? ? ? ? perror("bind");

? ? ? ? ? ? return 5;

? ? }

? ? /**************************************************************************/

? ? //填充ip端口信息

? ? client_addr.sin_family = AF_INET;

? ? client_addr.sin_port = htons(PORT);

? ? client_addr.sin_addr.s_addr = inet_addr(IP);

? ? //向服務(wù)端發(fā)起連接

? ? if (connect(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {

? ? ? ? perror("connect");

? ? ? ? return 2;

? ? }

? ? while(1) {

? ? ? ? memset(buf, 0, sizeof(buf));

? ? ? ? //從標(biāo)準(zhǔn)輸入獲取字符串

? ? ? ? if (!gets(buf)) {

? ? ? ? ? ? perror("gets");

? ? ? ? ? ? return 3;

? ? ? ? }

? ? ? ? if (strcmp(buf, "quit") == 0)

? ? ? ? ? ? break;

? ? ? ? if (write(client_fd, buf, strlen(buf)) != strlen(buf)) {

? ? ? ? ? ? perror("write");

? ? ? ? ? ? return 4;

? ? ? ? }

? ? }

? ? close(client_fd);

? ? return 0;

}

編譯運行后同樣使用netstat命令觀察連接狀態(tài),如下:

對比之前的截圖我們可以發(fā)現(xiàn),客戶端的ip和端口已經(jīng)變成我們設(shè)置的值。所以說,bind在客戶端也是可以使用的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 大綱 一.Socket簡介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,696評論 0 5
  • 下面為Daytime這個服務(wù)的源代碼例子,同時兼容IPV6和IPV4的地址,最后部分有更多說明。 單播模式下的Se...
    天楚銳齒閱讀 6,010評論 0 2
  • 網(wǎng)絡(luò)編程基礎(chǔ)網(wǎng)絡(luò)編程,首先了解計算機網(wǎng)絡(luò)體系結(jié)構(gòu)是有必要的,著重掌握TCP、IP協(xié)議,理解socket的概念,理解...
    zhile_doing閱讀 1,907評論 0 1
  • 1 預(yù)備知識 1.1 socket函數(shù) 為了執(zhí)行網(wǎng)絡(luò)輸入輸出,一個進(jìn)程必須做的第一件事就是調(diào)用socket函數(shù)獲得...
    Savior2016閱讀 2,982評論 0 4
  • UDP編程框架 由以上框圖可以看出: 客戶端要發(fā)起一次請求,僅僅需要兩個步驟(socket和sendto) 而服務(wù)...
    小葉大孟閱讀 953評論 0 0

友情鏈接更多精彩內(nèi)容