套接字編程---2(TCP套接字編程的流程,TCP套接字編程中的接口函數,TCP套接字的實現,TCP套接字出現的問題,TCP套接字多進程版本,TCP套接字多線程版本)

TCP模型創建流程圖

在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

TCP套接字編程中的接口

socket 函數

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain:

  1. AF_INET 這是大多數用來產生socket的協議,使用TCP或UDP來傳輸,用IPv4的地址
  2. AF_INET6 與上面類似,不過是來用IPv6的地址
  3. AF_UNIX 本地協議,使用在Unix和Linux系統上,一般都是當客戶端和服務器在同一臺及其上的時候使用

type:

  1. SOCK_STREAM 這個協議是按照順序的、可靠的、 數據完整的基于字節流的連接。 這是一個使用最多的socket類型,這個socket 是使用TCP來進行傳輸。
  2. SOCK_DGRAM 這個協議是無連接的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的連接。
  3. SOCK_SEQPACKET該協議是雙線路的、可靠的連接,發送固定長度的數據包進行傳輸。必須把這個包完整的接受才能進行讀取。
  4. SOCK_RAW socket類型提供單一的網絡訪問,這個socket類型使用ICMP公共協議。 (ping、traceroute使用該協議)
  5. SOCK_RDM 這個類型是很少使用的,在大部分的操作系統上沒有實現,它是提供給數據鏈路層使用,不保證數據包的順序

protocol:

傳0 表示使用默認協議。

返回值

成功:返回指向新創建的socket的文件描述符,失敗:返回-1,設置errno

socket函數的作用

socket()打開一個網絡通訊端口,如果成功的話,就像 open()一樣返回一個文件描述符,應用程序可以像讀寫文件一樣用 read/write 在網絡上收發數據,如果 socket()調用出錯則返回-1。對于 IPv4,domain 參數指定為 AF_INET。 對于 TCP 協議,type 參數指定為 SOCK_STREAM,表示面向流的傳輸協議。如果是 UDP 協議,則 type 參數指定為 SOCK_DGRAM,表示面向數據報的傳輸協議。protocol 參數的介紹從略,指定為 0 即可。

bind 函數

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:

socket文件描述符

addr:

構造出IP地址加端口號

addrlen:

sizeof(addr)長度 返回值: 成功返回0,失敗返回-1, 設置errno

bind函數的作用

  1. 服務器程序所監聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服務器程序的地址和端口號后就可 以向服務器發起連接,因此服務器需要調用 bind 綁定一個固定的網絡地址和端口號。

  2. **bind()的作用是將參數 sockfd 和 addr 綁定在一起,使 sockfd 這個用于網絡通訊的文件描述符監聽 addr 所描述 的地址和端口號。**前面講過,structsockaddr*是一個通用指針類型,addr 參數實際上可以接受多種協議的 sockaddr 結構體,而它們的長度各不相同,所以需要第三個參數 addrlen 指定結構體的長度。如:

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666);

  3. 首先將整個結構體清零,然后設置地址類型為 AF_INET網絡地址為 INADDR_ANY,這個宏表示本地的任意 IP 地址,因為服務器可能有多個網卡,每個網卡也可能綁定多個 IP 地址,這樣設置可以在所有的 IP 地址上監聽,直 到與某個客戶端建立了連接時才確定下來到底用哪個 IP 地址,端口號為 6666。

accept 函數

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockdf:

socket文件描述符

addr:

傳出參數,返回鏈接客戶端地址信息,含IP地址和端口號

addrlen:

傳入傳出參數(值-結果),傳入sizeof(addr)大小,函數返回時返回真正接收到地址結構體的大小

返回值:

成功返回一個新的socket文件描述符,用于和客戶端通信,失敗返回-1,設置errno

accpet函數的作用

三方握手完成后,服務器調用 accept()接受連接,如果服務器調用 accept()時還沒有客戶端的連接請求,就阻塞 等待直到有客戶端連接上來。addr 是一個傳出參數,accept()返回時傳出客戶端的地址和端口號。addrlen 參數是一 個傳入傳出參數(value-resultargument),傳入的是調用者提供的緩沖區 addr 的長度以避免緩沖區溢出問題,傳出 的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區) 。如果給 addr 參數傳 NULL,表示不關心 客戶端的地址。

示例

while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); ......close(connfd);} 

整個是一個 while 死循環,每次循環處理一個客戶端連接。由于 cliaddr_len 是傳入傳出參數,每次調用 accept() 之前應該重新賦初值。accept()的參數 listenfd是先前的監聽文件描述符,而 accept()的返回值是另外一個文件描述符 connfd,之后與客戶端之間就通過這個 connfd 通訊,最后關閉 connfd斷開連接,而不關閉 listenfd,再次回到循環 開頭 listenfd 仍然用作 accept 的參數。accept()成功返回一個文件描述符,出錯返回-1。

connect 函數

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

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

sockdf:

socket文件描述符

addr:

傳入參數,指定服務器端地址信息,含IP地址和端口號

addrlen:

傳入參數,傳入sizeof(addr)大小

返回值:

成功返回0,失敗返回-1,設置errno
客戶端需要調用 connect()連接服務器,connect 和 bind 的參數形式一致,區別在于 bind 的參數是自己的地址, 而 connect 的參數是對方的地址。connect()成功返回 0,出錯返回-1。

C/S 模型-TCP

在這里插入圖片描述

Tcp通信的實現

封裝接口

tcpsocket.hpp

/*                                                                                                                                                                                                               * 封裝Tcpsocket類,向外提供更加輕便的tcp套接字接口* 1.創建套接字     Socket()* 2.綁定地址信息   Bind(std::string &ip,uint16_t port)* 3.服務端開始監聽 Listen(int backlog = 5)* 4.服務端獲取已完成連接的客戶端socket     Accept(TcpSocket &clisock,std::string &cli_ip,uint16_t port)* 5.接受數據       Rec(std::string &buf)* 6.發送數據       Send(std::string &buf)* 7.關閉套接字     Close()* 8.客戶端向服務器發起連接請求 Connect(std::string &srv_ip,uint16_t srv_port)*/#include<iostream>
#include<string>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>#define CHECK_RET(q) if((q) == false){return -1;} class TcpSocket
{public:TcpSocket(){}   ~TcpSocket(){}   bool Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(_sockfd < 0){ perror("socket error");return false;}   return true;}   bool Bind(std::string &ip,uint16_t port){struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(struct sockaddr_in);int ret = bind(_sockfd, (struct sockaddr *)&addr ,len);if(ret < 0){perror("bind error");return false;}return true;}bool Listen(int backlog = 5){//int listen (int sockfd,int backlog)//sockfd: 套接字描述符//backlog:最大并發連接數--決定內核中已完成連接隊列結點個數//backlog決定的不是服務端能接受的客戶端最大上限int ret =listen(_sockfd,backlog);if(ret < 0){perror("listen error");return false;}return true;}bool Accept(TcpSocket &cli,std::string &cliip,uint16_t &port){//int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);//sockfd:套接字描述符//addr: 新建連接的客戶端地址信息//addrlen: 新建客戶端的地址信息長度//返回值:返回新建客戶端socket的描述符struct sockaddr_in addr;socklen_t len = sizeof(struct sockaddr_in);int sockfd = accept(_sockfd,(sockaddr*)&addr,&len);if(sockfd < 0){perror("accept error");return false;}cli.SetFd(sockfd);cliip = inet_ntoa(addr.sin_addr);port = ntohs(addr.sin_port);                               return true;                                                                                                                                                                                         }void SetFd(int sockfd){_sockfd = sockfd;}bool Connect(std::string &srv_ip,uint16_t srv_port){//int connect(int sockfd,const struct sockaddr *adrr,socklen_t addrlen);//sockfd :套接字描述符//addr:服務端地址信息//addrlen:地址信息長度struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(srv_port);addr.sin_addr.s_addr = inet_addr(srv_ip.c_str());socklen_t len = sizeof(struct sockaddr_in);int ret = connect(_sockfd,(struct sockaddr*)&addr,len);if(ret < 0){perror("connect error");return false;}return true;}bool Recv(std::string &buf){//ssize_t recv(int sockfd,void *buf ,size_t len,int flags)//sockfd :服務端為新客戶端新建的socket描述符//flags: 0--默認阻塞接受 沒有數據一直等待// MSG_PEEK 接受數據,但是數據并不從緩沖區移除// 常用于探測性數據接受//返回值:實際接收字節長度;出錯返回-1;若連接斷開則返回0//recv默認阻塞的,意味著沒有數據則一直等,不會返回0//返回0只有一種情況,就是連接斷開,不能再繼續通信了char tmp[4096];int ret = recv(_sockfd,tmp,4096,0);if(ret < 0){perror("recv error");return false;}else if(ret == 0){printf("peer shutdown\n");return false;}buf.assign(tmp,ret);return true;}bool Send(std::string &buf){//ssize_t send (int sockfd,void *buf,size_t len,int flags)int ret = send(_sockfd,buf.c_str(),buf.size(),0);if(ret < 0){perror("send error");return false;}return true;}bool Close(){close(_sockfd);return true;}private:int _sockfd;
};       

客戶端實現

/**基于封裝的Tcpsocket實現tco客戶端程序1.創建套接字2.為套接字綁定地址信息(不推薦用戶手動綁定)while(1){
4.發送數據
5.接收數據}6.關閉套接字*/#include"tcpsocket.hpp"int main(int argc,char *argv[]){if(argc != 3){ std::cout<<"./tco_cli 192.168.145.132 9000\n";return -1; }   std::string ip = argv[1];uint16_t port = atoi(argv[2]);TcpSocket sock;CHECK_RET(sock.Socket());CHECK_RET(sock.Connect(ip,port));while(1){std::string buf;std::cout<<"client say:";fflush(stdout);std::cin >> buf;sock.Send(buf);buf.clear();sock.Recv(buf);std::cout<<"server say:"<<buf<<std::endl;                                                                                                                                                                }   sock.Close();return 0;
}

服務端的實現

/**基于封裝的tcpsocket,實現tcp服務端程序1. 創建套接字2.為套接字綁定地址信息3.開始監聽,如果有連接進來,自動完成三次握手while(1){4,從已完成連接隊列,獲取新建的客戶端連接socket5.通過新建的客戶端連接socket,與指定的客戶端進行通信,recv6.send}7. 關閉套接字*/#include"tcpsocket.hpp"int main(int argc,char *argv[]){if(argc != 3){ std::cout<<"./tcp_srv 192.168.145.132 9000\n";return -1; }   std::string ip =argv[1];uint16_t port =atoi(argv[2]);TcpSocket sock;CHECK_RET(sock.Socket());CHECK_RET(sock.Bind(ip,port));CHECK_RET(sock.Listen());while(1){TcpSocket clisock;std::string cliip;uint16_t cliport; if(sock.Accept(clisock,cliip,cliport) == false){    //當已完成的連接隊列中沒有socket,會阻塞                                                                                                             continue;}   std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;std::string buf;clisock.Recv(buf);std::cout<<"client say:"<<buf<<std::endl;buf.clear();std::cout<<"server say:";fflush(stdout);std::cin>>buf;clisock.Send(buf);}sock.Close();
}     

程序出現的問題

這個實現的最基本的tcp服務端程序中,因為服務端不知道客戶端數據什么時候到來,因此程序只能寫死;但是寫死就有可能會造成阻塞(accep/recv),導致服務端無法同時處理多個客戶端的請求

多進程tcp服務端程/多線程版本tcp服務端程序

適用多進程tcp服務端程序的處理多客戶端請求;每當一個客戶端的連接到來,都創建一個新的子進程,讓子進程單獨與客戶端進行通信;這樣的話父進程永遠只處理新連接

TCP套接字多進程版本

#include<signal.h>
#include"tcpsocket.hpp"
#include<sys/wait.h>void sigcb(int no){while(waitpid(-1,NULL,WNOHANG) > 0);
}int main(int argc,char *argv[]){if(argc != 3){std::cout<<"./tcp_srv 192.168.145.132 9000\n";return -1;}std::string ip =argv[1];uint16_t port =atoi(argv[2]);signal(SIGCHLD,sigcb);TcpSocket sock;CHECK_RET(sock.Socket());CHECK_RET(sock.Bind(ip,port));CHECK_RET(sock.Listen());while(1){TcpSocket clisock;std::string cliip;uint16_t cliport; if(sock.Accept(clisock,cliip,cliport) == false){    //當已完成的連接隊列中沒有socket,會阻塞continue;}std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;int pid =fork();if(pid == 0){//子進程專門處理每個客戶端的通信while(1){std::string buf;clisock.Recv(buf);std::cout<<"client say:"<<buf<<std::endl;buf.clear();std::cout<<"server say:";fflush(stdout);std::cin>>buf;clisock.Send(buf);}clisock.Close();exit(0);}//父進程關閉套接字,因為父子進程數據獨有//不關閉,會造成資源泄露clisock.Close();//父進程不通信}sock.Close();
}        

TCP套接字多線程版本

#include"tcpsocket.hpp"
#include<pthread.h>
void* thr_start(void *arg){TcpSocket *clisock = (TcpSocket *)arg;while(1){//因為線程之間,共享文件描述符表,因此在一個線程中打開的文件//另一個線程只要能夠獲取文件描述符,就能在操作文件std::string buf;clisock->Recv(buf);std::cout<<"client say:"<<buf<<std::endl;buf.clear();std::cout<<"server say:";fflush(stdout);std::cin>>buf;clisock->Send(buf);}clisock->Close();delete clisock;return NULL;
}int main(int argc,char *argv[]){if(argc != 3){std::cout<<"./tcp_srv 192.168.145.132 9000\n";return -1;}std::string ip =argv[1];uint16_t port =atoi(argv[2]);TcpSocket sock;CHECK_RET(sock.Socket());CHECK_RET(sock.Bind(ip,port));CHECK_RET(sock.Listen());while(1){TcpSocket *clisock = new TcpSocket();std::string cliip;uint16_t cliport; if(sock.Accept(*clisock,cliip,cliport) == false){    //當已完成的連接隊列中沒有socket,會阻塞continue;}std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;pthread_t tid;pthread_create(&tid,NULL,thr_start,(void *)clisock);pthread_detach(tid);}sock.Close();
}   

tcp連接斷開

tcp自己實現了保活機制:當長時間沒有數據通信,服務端會向客戶端發送保活探測包;當這些保活探測包連續多次都沒有響應,則認為連接斷開
recv返回0;send觸發異常

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383199.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383199.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383199.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

安全關注

http://yttitan.blog.51cto.com/70821/1576365

Linux中netstat工具詳解

簡介 Netstat 命令用于顯示各種網絡相關信息&#xff0c;如網絡連接&#xff0c;路由表&#xff0c;接口狀態 (Interface Statistics)&#xff0c;masquerade 連接&#xff0c;多播成員 (Multicast Memberships) 等等。 常見參數 -a (all)顯示所有選項&#xff0c;默認不顯示…

網絡基礎 2-1(應用層,HTTP三點注意,HTTP協議格式, 最簡單的HTTP服務器)

應用層 應用層 負責應用程序之間的數據溝通-----協議都是用戶自己定的 自定制協議&#xff1a; 結構化數據傳輸 序列化&#xff1a; 將數據對象以指定的協議&#xff08;數據格式&#xff09;進行可用于持久化存儲或者數據傳輸時的數據組織 例如在分布式的系統中&#xf…

正則表達式 學習

http://regexr.com/ 在線匹配 http://c.biancheng.net/cpp/html/1434.html 查詢網址

網絡基礎2-2(傳輸層,端口,詳談UDP)

傳輸層 負責數據能夠從發送端傳輸接收端. 端口號 端口號(Port)標識了一個主機上進行通信的不同的應用程序;在TCP/IP協議中, 用 “源IP”, “源端口號”, “目的IP”, “目的端口號”, “協議號” 這樣一個五元組來標識一個通信(可以通過 netstat -n查看);一個端口只能被一個…

中文rfc文檔路徑

http://man.chinaunix.net/develop/rfc/default.htm

網絡基礎2-3(TCP協議,三次握手,四次揮手,TIME_WAIT狀態的作用,TCP如何保證可靠傳輸,TCP連接中狀態轉化,滑動窗口,流量控制,快速重傳,擁塞窗口,延遲應答,捎帶應答,粘包問題)

TCP協議 TCP協議概念 TCP全稱為 “傳輸控制協議(Transmission Control Protocol”). 人如其名, 要對數據的傳輸進行一個詳細的控制 TCP協議格式 1. 源/目的端口號: 表示數據是從哪個進程來, 到哪個進程去; 2. 32位序號/32位確認號: 后面詳細講; 3. 4位TCP報頭長度: 表示該…

超時設置

//read操作加上超時時間。1 int read_timeout(int fd, void *buf, uint32_t count, int time)2 {3 if(time > 0) {4 fd_set rSet;5 FD_ZERO(&rSet);6 FD_SET(fd, &rSet);7 8 struct timeval timeout;9 memset(&tim…

字符串題目 1 --------判斷兩個字符串是否為旋轉詞

題目描述 如果一個字符串為str&#xff0c;把字符串的前面任意部分挪到后面形成的字符串交str的旋轉詞。比如str“12345”&#xff0c;str的旋轉串有“12345”、“45123”等等。給定兩個字符串&#xff0c;判斷是否為旋轉詞。 輸入描述: 輸出包含三行&#xff0c;第一個兩個…

2021-03-04

為什么nginx轉發后端默認使用1.0而不是1.1 在 Nginx 的官網文檔中&#xff0c;有這樣一個指令&#xff1a; Syntax: gzip_http_version 1.0 | 1.1; Default: gzip_http_version 1.1; Context: http, server, location Sets the minimum HTTP version of a request required to…

字符串題目---2判斷兩個字符串是否為變形詞

題目描述 給定兩個字符串str1和str2&#xff0c;如果str1和str2中出現的字符種類出現的一樣且每種字符出現的次數也一樣&#xff0c;那么str1和str2互為變形詞。請判斷str1和str2是否為變形詞 輸入描述: 輸入包括3行&#xff0c;第一行包含兩個整數n&#xff0c;m(1 \leq n,…

設計模式7----代理模式

代理模式 概念 Proxy 模式又叫做代理模式&#xff0c;是結構型的設計模式之一&#xff0c;它可以為其他對象提供一 種代理&#xff08;Proxy&#xff09;以控制對這個對象的訪問。 所謂代理&#xff0c;是指具有與代理元&#xff08;被代理的對象&#xff09;具有相同的接口的…

網絡基礎3-1(細談IP協議頭, 網絡層,子網劃分,路由選擇,數據鏈路層,以太網幀格式,MAC地址,再談ARP協議)

IP協議 IP協議頭格式 4位版本號(version): 指定IP協議的版本, 對于IPv4來說, 就是44位頭部長度(header length): IP頭部的長度是多少個。32bit, 也就是 length * 4 的字節數. 4bit表示大 的數字是15, 因此IP頭部大長度是60字節8位服務類型(Type Of Service): 3位優先權字段(已…

c++常見并且必須記住的問題

一、基礎知識 基本語言 1、說一下static關鍵字的作用 ?2、說一下C和C的區別 3、說一說c中四種cast轉換 4、請說一下C/C 中指針和引用的區別&#xff1f; 5、給定三角形ABC和一點P(x,y,z)&#xff0c;判斷點P是否在ABC內&#xff0c;給出思路并手寫代碼 6、怎么判斷一個…

網絡中典型協議--(DNS,輸入url后, 發生的事情. ,ICMP,NAT)

DNS&#xff08;Domain Name System&#xff09; DNS是一整套從域名映射到IP的系統 域名服務器發展背景 TCP/IP中使用IP地址和端口號來確定網絡上的一臺主機的一個程序. 但是IP地址不方便記憶. 于是人們發明了一種叫主機名的東西, 是一個字符串, 并且使用hosts文件來描述主機…

高級IO--1 ---(五種典型IO,阻塞IO,非阻塞IO,信號驅動IO,異步IO, IO多路轉接)

高級IO&#xff1a; 五種典型IO&#xff1a; 阻塞IO/非阻塞IO/信號驅動IO/異步IO/IO多路轉接 IO多路轉接模型&#xff1a;select/poll/epoll 五種典型IO 阻塞IO IO操作的流程&#xff1a;等待IO操作條件具備&#xff0c;然后進行數據拷貝 為了完成IO操作發起調用&#xff…

IO多路轉接模型----(select的模型,select的優缺點,poll的模型,poll的優缺點)

IO多路轉接模型&#xff1a;select/poll/epoll 對大量描述符進行事件監控(可讀/可寫/異常) select模型 用戶定義描述符的事件監控集合 fd_set&#xff08;這是一個位圖&#xff0c;用于存儲要監控的描述符&#xff09;; 用戶將需要監控的描述符添加到集合中&#xff0c;這個描…

IO多路轉接模型-----epoll

epoll&#xff1a; Linux下性能最高的多路轉接模型 epoll 有3個相關的系統調用. epoll_create 功能&#xff1a;創建epoll&#xff0c;在內核中創建eventpoll結構體&#xff0c;size決定了epoll最多監控多少個描述符&#xff0c;在Linux2.6.8之后被忽略&#xff0c;但是必須…

再寫順序表(c語言實現,外加冒泡排序,二分查找)

概念 順序表是用一段物理地址連續的存儲單元依次存儲數據元素的線性結構&#xff0c;一般情況下采用數組存儲。在數組 上完成數據的增刪查改。 順序表一般可以分為&#xff1a; 靜態順序表&#xff1a;使用定長數組存儲。動態順序表&#xff1a;使用動態開辟的數組存儲。 頭…

再寫單鏈表(不帶頭單鏈表)

單鏈表 實際中鏈表的結構非常多樣&#xff0c;以下情況組合起來就有8種鏈表結構&#xff1a; 單向、雙向帶頭、不帶頭循環、非循環 雖然有這么多的鏈表的結構&#xff0c;但是我們實際中最常用還是兩種結構&#xff1a; 無頭單向非循環鏈表&#xff1a;結構簡單&#xff0…