套接字编程---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.mzph.cn/news/383199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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…

网络基础2-2(传输层,端口,详谈UDP)

传输层 负责数据能够从发送端传输接收端. 端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序;在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n查看);一个端口只能被一个…

网络基础2-3(TCP协议,三次握手,四次挥手,TIME_WAIT状态的作用,TCP如何保证可靠传输,TCP连接中状态转化,滑动窗口,流量控制,快速重传,拥塞窗口,延迟应答,捎带应答,粘包问题)

TCP协议 TCP协议概念 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制 TCP协议格式 1. 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去; 2. 32位序号/32位确认号: 后面详细讲; 3. 4位TCP报头长度: 表示该…

字符串题目 1 --------判断两个字符串是否为旋转词

题目描述 如果一个字符串为str&#xff0c;把字符串的前面任意部分挪到后面形成的字符串交str的旋转词。比如str“12345”&#xff0c;str的旋转串有“12345”、“45123”等等。给定两个字符串&#xff0c;判断是否为旋转词。 输入描述: 输出包含三行&#xff0c;第一个两个…

字符串题目---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位优先权字段(已…

网络中典型协议--(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;但是必须…

再写单链表(不带头单链表)

单链表 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 单向、双向带头、不带头循环、非循环 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常用还是两种结构&#xff1a; 无头单向非循环链表&#xff1a;结构简单&#xff0…

再写双向循环链表

#pragma once #include<assert.h> #include<malloc.h> #include<stdio.h> typedef int DLDataType;//定义链表结点结构 typedef struct DListNode{DLDataType value;struct DListNode *prev; //指向前一个结点struct DListNode *next; //指向后一个结点 } DL…

链表题目--1 删除链表中所有等于val的值

注意事项 要删除的结点相邻第一个结点就是要删除的结点 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* removeElements(struct ListNode* head, int val){if(headNULL){return NULL;}struct …

链表题目--2 求链表的中间结点 和 求链表中倒数第k个结点

求链表的中间结点 思路 一个走两步&#xff0c;一个走一步。一个走到尾&#xff0c;另外一个就走到了中间 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* middleNode(struct ListNode* head…

链表题目---3 合并两个有序单链表 和 分割链表

合并两个有序单链表 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){if(l1 NULL){return l2;}else if(l2 NULL){return l1;}struc…

链表题目---4 删除链表中重复的结点 和 判断链表是否为回文链表

删除链表中重复的结点 /* struct ListNode {int val;struct ListNode *next;ListNode(int x) :val(x), next(NULL) {} }; */ class Solution { public:ListNode* deleteDuplication(ListNode* pHead){if(pHead NULL){return NULL;}ListNode *prev NULL; //用于删除的结点, 是…

链表题目----5 相交链表 和 环形链表 和 返回链表开始入环的第一个节点

相交链表 思路 链表交叉不可能是x型因为有可能两个链表不等长&#xff0c;所以我们必须让他们从同一起跑位置去起跑从同一起跑位置出发&#xff0c;依次比较每个结点的地址是否相同 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct L…

链表题目---6 复制带随机指针的链表

思路 将新结点放在老结点的后面 复制random 将链表拆开 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node() {}Node(int _val, Node* _next, Node* _random) {val _val;next _next;random _random;} }; */ class Solution { publi…