日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux网络编程】TCP

發(fā)布時間:2024/4/21 linux 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Linux网络编程】TCP 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

概述

TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。


TCP 具有以下特點(diǎn):

1)電話系統(tǒng)服務(wù)模式的抽象

2)每一次完整的數(shù)據(jù)傳輸都要經(jīng)過建立連接、使用連接、終止連接的過程

3)可靠、出錯重傳、且每收到一個數(shù)據(jù)都要給出相應(yīng)的確認(rèn),保證數(shù)據(jù)傳輸?shù)目煽啃?/span>


TCP 編程的 C/S 架構(gòu)

基于 TCP 的網(wǎng)絡(luò)編程開發(fā)分為服務(wù)器端和客戶端兩部分,常見的核心步驟和流程如下:

TCP 客戶端編程


對于 TCP 客戶端編程流程,有點(diǎn)類似于打電話過程:找個可以通話的手機(jī)(socket()?)?->?撥通對方號碼并確定對方是自己要找的人(?connect()?)?->?主動聊天(?send()?或?write()?)->?或者,接收對方的回話(?recv()?或?read()?)->?通信結(jié)束后,雙方說再見掛電話(close()?)。


所需頭文件:#include <sys/socket.h>
int socket(int family,int type,int protocol);
功能: 創(chuàng)建一個用于網(wǎng)絡(luò)通信的 socket套接字(描述符),詳細(xì)用法,請看《套接字的介紹》
參數(shù): family:本示例寫?AF_INET,代表 IPv4 type:本示例寫?SOCK_STREAM,代表 TCP 數(shù)據(jù)流 protocol:這里寫 0,設(shè)為 0 表示使用默認(rèn)協(xié)議
返回值: 成功:套接字 失敗 < 0?

int connect( int sockfd,?const struct sockaddr *addr,?socklen_t len );

功能:

主動跟服務(wù)器建立連接,有點(diǎn)類似于,我們給別人電話,主動撥對方的電話號碼,具體是怎么一個過程,請《connect()、listen()和accept()三者之間的關(guān)系》

參數(shù):

sockfdsocket()返回的套接字

addr:連接的服務(wù)器地址結(jié)構(gòu)

len:地址結(jié)構(gòu)體長度

返回值:

成功:0 ? ?

失敗:-1


connect() 函數(shù)相當(dāng)于撥號碼,只有撥通號碼并且確定對方是自己要找的人(三次握手才能進(jìn)行下一步的通信


ssize_t send(int sockfd,?const void* buf,?size_t nbytes,?int flags);

功能:

發(fā)送數(shù)據(jù),最后一個參數(shù)為 0 時,可以用 write() 替代(?send 等同于 write?)。注意:不能用 TCP 協(xié)議發(fā)送 0 長度的數(shù)據(jù)包。假如,數(shù)據(jù)沒有發(fā)送成功,內(nèi)核會自動重發(fā)。

參數(shù):

sockfd: 已建立連接的套接字

buf?發(fā)送數(shù)據(jù)的地址

nbytes: 發(fā)送緩數(shù)據(jù)的大小(以字節(jié)為單位)

flags?套接字標(biāo)志(常為 0)

返回值:

成功:成功發(fā)送的字節(jié)數(shù)

失敗 < 0



虛擬機(jī)中Redhat5.5的 TCP 客戶端程序代碼:

#include <stdio.h> #include <stdlib.h> #include <string.h>#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>#define PORT 10086 #define SIZE 128/***服務(wù)端:* a. 創(chuàng)建一個套字* b. 初始化結(jié)構(gòu)體* c. 綁定* d. 監(jiān)聽 設(shè)置監(jiān)聽隊(duì)列的大小* e. 接受客戶端的連接* f. 讀寫操作**/int main(void) {int sockfd = 0;int newfd = 0;int ret = -1;char buf[SIZE];struct sockaddr_in sockaddr;struct sockaddr_in fromaddr;socklen_t len = sizeof(fromaddr);//a. 創(chuàng)建套接子sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("socket"); goto err0;}//b. 初始化結(jié)構(gòu)體memset(&sockaddr, 0, sizeof(sockaddr));sockaddr.sin_family = AF_INET; //ipv4sockaddr.sin_port = htons(PORT);sockaddr.sin_addr.s_addr = inet_addr("172.16.1.88");//綁定ret = bind(sockfd, (void*)&sockaddr, sizeof(sockaddr));if (-1 == ret){perror("bind"); goto err1;}//監(jiān)聽ret = listen(sockfd, 10);if (-1 == ret){perror("listen"); goto err1;}printf("server is waiting the client incomming....\n");//阻塞 接受客戶端連接newfd = accept(sockfd, (void*)&fromaddr, &len);if (-1 == newfd){perror("accept"); goto err1;}//客戶端的信息printf("from client ip: %s port: %d\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port));//讀寫操作while(1){memset(buf, 0, SIZE); //ret = write(newfd, "hello world", 11); ret = send(newfd, "hello world", 11, 0);if (ret <= 0)break;printf("send %d bytes\n", ret);sleep(1);}close(sockfd);return 0; err1:close(sockfd); err0:return -1; }

運(yùn)行結(jié)果如下:



對于客戶端,也是可以接收數(shù)據(jù),前提為,客戶端先給服務(wù)器發(fā)送數(shù)據(jù)。

ssize_t recv(int sockfd,?void *buf, ?size_t nbytes,?int flags);

功能:

接收網(wǎng)絡(luò)數(shù)據(jù),默認(rèn)的情況下,如果沒有接收到數(shù)據(jù),這個函數(shù)會阻塞,直到有數(shù)據(jù)到來。

參數(shù):

sockfd:套接字

buf接收網(wǎng)絡(luò)數(shù)據(jù)的緩沖區(qū)的地址

nbytes:接收緩沖區(qū)的大小(以字節(jié)為單位)

flags套接字標(biāo)志(常為 0 )

返回值:

成功:成功接收的字節(jié)數(shù)

失敗 < 0


測試代碼如下:

#include <stdio.h> #include <stdlib.h> #include <string.h>#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>#define PORT 10086 #define SIZE 128/***客戶端:* a. 創(chuàng)建一個套接子* b. 初始化結(jié)構(gòu)體* e. 鏈接服務(wù)器* f. 讀寫操作**/int main(void) {int sockfd = 0;int newfd = 0;int ret = -1;char buf[SIZE];struct sockaddr_in sockaddr;socklen_t len = sizeof(sockaddr);//a. 創(chuàng)建套接子sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("socket"); goto err0;}//b. 初始化結(jié)構(gòu)體 使用服務(wù)器相關(guān)信息memset(&sockaddr, 0, sizeof(sockaddr));sockaddr.sin_family = AF_INET; //ipv4sockaddr.sin_port = htons(PORT);sockaddr.sin_addr.s_addr = inet_addr("172.16.1.88");//c. 連接服務(wù)器ret = connect(sockfd, (void*)&sockaddr, len);if (-1 == ret){perror("connect"); goto err1;}printf("connect to server successfully....\n");//讀寫操作while(1){memset(buf, 0, SIZE); //ret = read(sockfd, buf, SIZE);ret = recv(sockfd, buf, SIZE, 0);if (ret <= 0)break;buf[ret] = 0;printf("from server: %s\n", buf);}close(sockfd);return 0; err1:close(sockfd); err0:return -1; }

運(yùn)行結(jié)果如下:



TCP 服務(wù)器編程

做為 TCP 服務(wù)器需要具備的條件呢?

  • 具備一個可以確知的地址( bind() ):相當(dāng)于我們要明確知道移動客服的號碼,才能給他們電話;
  • 讓操作系統(tǒng)知道是一個服務(wù)器,而不是客戶端( listen() ):相當(dāng)于移動的客服,他們主要的職責(zé)是被動接聽用戶電話,而不是主動打電話騷擾用戶;
  • 等待連接的到來( accept() ):移動客服時刻等待著,來一個客戶接聽一個。

接收端使用 bind() 函數(shù),來完成地址結(jié)構(gòu)與socket 套接字的綁定,這樣 ip、port 就固定了,發(fā)送端即可發(fā)送數(shù)據(jù)給有明確地址( ip+port ) 的接收端。


對于 TCP 服務(wù)器編程流程,有點(diǎn)類似于接電話過程:找個可以通話的手機(jī)(socket()?)?->?插上電話卡固定一個號碼(?bind()?)?->?職責(zé)為被動接聽,給手機(jī)設(shè)置一個鈴聲來監(jiān)聽是否有來電(?listen()?)?->?有來電,確定雙方的關(guān)系后,才真正接通不掛電話(?accept()?)?->?接聽對方的訴說(?recv()?)?->?適當(dāng)給些回話(?send()?)->?通信結(jié)束后,雙方說再見掛電話(?close()?)。


int bind( int sockfd,?const struct sockaddr *myaddr,socklen_t addrlen );

功能:

將本地協(xié)議地址與 sockfd 綁定,這樣 ip、port 就固定了

參數(shù):

sockfdsocket 套接字

myaddr: 指向特定協(xié)議的地址結(jié)構(gòu)指針

addrlen:該地址結(jié)構(gòu)的長度

返回值:

成功:返回 0

失敗:-1


使用實(shí)例如下:

// 本地網(wǎng)絡(luò)地址 struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); // 清空結(jié)構(gòu)體內(nèi)容 my_addr.sin_family = AF_INET; // ipv4 my_addr.sin_port = htons(port); // 端口轉(zhuǎn)換 my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定網(wǎng)卡所有ip地址,INADDR_ANY為通配地址,值為0printf("Binding server to port %d\n", port); int err_log; err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 綁定 if(err_log != 0) {perror("bind");close(sockfd); exit(-1); }

int listen(int sockfd, int backlog);

功能:

將套接字由主動修改為被動,使操作系統(tǒng)為該套接字設(shè)置一個連接隊(duì)列,用來記錄所有連接到該套接字的連接。更詳細(xì)說明,請看《connect()、listen()和accept()三者的關(guān)系》

參數(shù):

sockfd: socket監(jiān)聽套接字

backlog:連接隊(duì)列的長度

返回值:

成功:返回0

失敗:其他


int accept( ?int sockfd,?struct sockaddr *cliaddr,?socklen_t *addrlen );

功能:

從已連接隊(duì)列中取出一個已經(jīng)建立的連接,如果沒有任何連接可用,則進(jìn)入睡眠等待(阻塞)。更詳細(xì)說明,請看《connect()、listen()和accept()三者的關(guān)系》

參數(shù):

sockfd: socket監(jiān)聽套接字

cliaddr: 用于存放客戶端套接字地址結(jié)構(gòu)

addrlen:套接字地址結(jié)構(gòu)體長度的地址

返回值:

成功:已連接套接字。注意:返回的是一個已連接套接字,這個套接字代表當(dāng)前這個連接

失敗:< 0


Redhat 中的服務(wù)器代碼如下:

#include <stdio.h> #include <stdlib.h> #include <string.h>#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>#define PORT 10086 #define SIZE 128/***服務(wù)端:* a. 創(chuàng)建一個套字* b. 初始化結(jié)構(gòu)體* c. 綁定* d. 監(jiān)聽 設(shè)置監(jiān)聽隊(duì)列的大小* e. 接受客戶端的連接* f. 讀寫操作**/int main(void) {int sockfd = 0;int newfd = 0;int ret = -1;char buf[SIZE];struct sockaddr_in sockaddr;struct sockaddr_in fromaddr;socklen_t len = sizeof(fromaddr);//a. 創(chuàng)建套接子sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("socket"); goto err0;}//b. 初始化結(jié)構(gòu)體memset(&sockaddr, 0, sizeof(sockaddr));sockaddr.sin_family = AF_INET; //ipv4sockaddr.sin_port = htons(PORT);sockaddr.sin_addr.s_addr = inet_addr("172.16.1.88");//綁定ret = bind(sockfd, (void*)&sockaddr, sizeof(sockaddr));if (-1 == ret){perror("bind"); goto err1;}//監(jiān)聽ret = listen(sockfd, 10);if (-1 == ret){perror("listen"); goto err1;}printf("server is waiting the client incomming....\n");//阻塞 接受客戶端連接newfd = accept(sockfd, (void*)&fromaddr, &len);if (-1 == newfd){perror("accept"); goto err1;}//客戶端的信息printf("from client ip: %s port: %d\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port));//讀寫操作while(1){memset(buf, 0, SIZE); //ret = write(newfd, "hello world", 11); ret = send(newfd, "hello world", 11, 0);if (ret <= 0)break;printf("send %d bytes\n", ret);sleep(1);}close(sockfd);return 0; err1:close(sockfd); err0:return -1; }


Windows 的網(wǎng)絡(luò)調(diào)試助手作為 TCP 客戶端,給 ubuntu 中的服務(wù)器發(fā)送數(shù)據(jù),運(yùn)行結(jié)果如下:



關(guān)閉連接:close()

使用 close() 函數(shù)即可關(guān)閉套接字,關(guān)閉一個代表已連接套接字將導(dǎo)致另一端接收到一個 0 長度的數(shù)據(jù)包,詳情請看《 TCP 四次揮手》


做服務(wù)器時

  • 關(guān)閉監(jiān)聽套接字( socket()和listen()之后的套接字 )將導(dǎo)致服務(wù)器無法接收新的連接,但不會影響已經(jīng)建立的連接;
  • 關(guān)閉 accept()返回的已連接套接字將導(dǎo)致它所代表的連接被關(guān)閉,但不會影響服務(wù)器的監(jiān)聽( socket()和listen()之后的套接字 )。


做客戶端時

關(guān)閉連接就是關(guān)閉連接,不意味著其他。


如果客戶端和服務(wù)器已經(jīng)連接成功的前提下,通常的情況下,先關(guān)閉客戶端,再關(guān)閉服務(wù)器,如果是先關(guān)閉服務(wù)器,立馬啟動服務(wù)器是,服務(wù)器綁定的端口不會立馬釋放(如下圖),要過 1 分鐘左右才會釋放,為什么會這樣的呢?請看《 TCP 四次揮手》。有沒有方法讓服務(wù)器每次啟動都能立即成功?請看《端口復(fù)用》


總結(jié)

以上是生活随笔為你收集整理的【Linux网络编程】TCP的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。