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

背景

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

功能介紹

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

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

代碼

1袜匿、先看服務端代碼

/*服務端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、向服務端發(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);

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

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

? ? ? ? perror("connect");

? ? ? ? return 2;

? ? }

? ? while(1) {

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

? ? ? ? //從標準輸入獲取字符串

? ? ? ? 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)如下:

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

探討

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

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

accept_fd = accept(listen_fd, NULL, NULL);

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

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

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

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

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

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

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

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

我們將上面客戶端的程序稍微修改一下端幼,增加bind操作。為了更好的看出bind的結(jié)果弧满,我在環(huán)境上配了兩個ip婆跑,讓客戶端和服務端各在一個ip上進行通信。我們配置客戶端使用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);

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

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

? ? ? ? perror("connect");

? ? ? ? return 2;

? ? }

? ? while(1) {

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

? ? ? ? //從標準輸入獲取字符串

? ? ? ? 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)變成我們設置的值扶关。所以說,bind在客戶端也是可以使用的数冬。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末节槐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铜异,老刑警劉巖哥倔,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異揍庄,居然都是意外死亡咆蒿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門币绩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜡秽,“玉大人,你說我怎么就攤上這事缆镣⊙客唬” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵董瞻,是天一觀的道長寞蚌。 經(jīng)常有香客問我,道長钠糊,這世上最難降的妖魔是什么挟秤? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮抄伍,結(jié)果婚禮上艘刚,老公的妹妹穿的比我還像新娘。我一直安慰自己截珍,他們只是感情好攀甚,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岗喉,像睡著了一般秋度。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钱床,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天荚斯,我揣著相機與錄音,去河邊找鬼查牌。 笑死事期,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的纸颜。 我是一名探鬼主播兽泣,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼懂衩!你這毒婦竟也來了撞叨?” 一聲冷哼從身側(cè)響起金踪,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牵敷,沒想到半個月后胡岔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡枷餐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年靶瘸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毛肋。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡怨咪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出润匙,到底是詐尸還是另有隱情诗眨,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布孕讳,位于F島的核電站匠楚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏厂财。R本人自食惡果不足惜芋簿,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望璃饱。 院中可真熱鬧与斤,春花似錦、人聲如沸荚恶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裆甩。三九已至冗锁,卻和暖如春齐唆,著一層夾襖步出監(jiān)牢的瞬間嗤栓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工箍邮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茉帅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓锭弊,卻偏偏與公主長得像堪澎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子味滞,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

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