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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

套接字编程---2(TCP套接字编程的流程,TCP套接字编程中的接口函数,TCP套接字的实现,TCP套接字出现的问题,TCP套接字多进程版本,TCP套接字多线程版本)

發布時間:2023/11/30 编程问答 30 豆豆

TCP模型創建流程圖


TCP套接字編程中的接口

socket 函數

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

domain:

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

  • SOCK_STREAM 這個協議是按照順序的、可靠的、 數據完整的基于字節流的連接。 這是一個使用最多的socket類型,這個socket 是使用TCP來進行傳輸。
  • SOCK_DGRAM 這個協議是無連接的、固定長度的傳輸調用。該協議是不可靠的,使用UDP來進行它的連接。
  • SOCK_SEQPACKET該協議是雙線路的、可靠的連接,發送固定長度的數據包進行傳輸。必須把這個包完整的接受才能進行讀取。
  • SOCK_RAW socket類型提供單一的網絡訪問,這個socket類型使用ICMP公共協議。 (ping、traceroute使用該協議)
  • 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函數的作用

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

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

  • 首先將整個結構體清零,然后設置地址類型為 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觸發異常

    總結

    以上是生活随笔為你收集整理的套接字编程---2(TCP套接字编程的流程,TCP套接字编程中的接口函数,TCP套接字的实现,TCP套接字出现的问题,TCP套接字多进程版本,TCP套接字多线程版本)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。