700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 网络基础:套接字编程 UDP和TCP通信程序

网络基础:套接字编程 UDP和TCP通信程序

时间:2023-11-16 08:16:48

相关推荐

网络基础:套接字编程 UDP和TCP通信程序

文章目录

字节序套接字编程客户端与服务端★ netstat命令 ★UDP通信程序通信流程:接口:流程外的重要接口:服务端代码客户端代码TCP通信程序通信流程:接口:代码实例:出现问题:解决方案:优化后的代码

字节序

字节序:cpu对内存中数据进行存取的顺序

主机字节序的分类:小端、大端

小端:低地址存低位

大端:低地址存高位

编写代码判断主机字节序:

#include<iostream>using namespace std;void check_sys1(){int a = 1;char* b = (char*)&a;if (*b == 1)cout << "小端" << endl;if (*b == 0)cout << "大端" << endl;}void check_sys2(){//联合类型的所有数据共用一块内存,内存大小根据最大的数据类型决定union UN {int a;char b;}u;u.a = 1;if(u.b==1)cout << "小端" << endl;if(u.b==0)cout << "大端" << endl;}int main(){check_sys1();check_sys2();return 0;}

主机字节序对网络通信的影响:如果通信两端主机字节序不同,可能会造成数据二义性。

解决方案:订立网络通信字节序标准,规定网络中的数据都按照网络字节序进行存取。网络字节序-----其实是大端字节序

发送方将数据转换成网络字节序后进行发送,接收方根据自己的的主机字节序将接收到的数据进行转换。

字节序只针对存储单元大于一个字节的数据类型。

注意:字符串实际上是单字节存储,所以不需要转换。

套接字编程

socket 套接字编程:网络通信程序的编写

分类:UDP协议通信程序的/TCP协议通信程序

区别:

UDP协议:用户数据报协议

特点:无连接,不可靠,面向数据报

应用场景:实时性要求大于安全性要求----例如视频传输

TCP协议:传输控制协议

特点:面向连接,可靠,面向字节流

应用场景:安全性要求大于实时性要求----例如文件传输

客户端与服务端

在网络通信程序中,通信两端被分为客户端和服务端,

客户端:提供给客户的通信段,通常是通信程序中主动发起请求的一端。

客户端必须提前知道服务端的地址信息(ip地址+port端口)才能发送请求,通常是被提前写在应用程序中,并且通常是固定不变的。

服务端:通常是指被动接受请求,提供服务的通信端。

★ netstat命令 ★

netstat命令:查看当前网络状态信息

-a:查看所有

-t :查看TCP信息

-u:查看UDP信息

-n:不以服务名称显示,以具体地址端口显示

-p:查看当前网络状态对应的进程

UDP通信程序

通信流程:

接口:

1、创建套接字:int socket(int domain,int type,int protocol);

domain:地址域类型----指定使用的是什么样的地址结构:AF_INET----IPV4通信,使用IPV4地址结构

type:套接字类型;SOCK_STREAM:流式套接字 / SOCK_DGRAM:数据报套接字

注意:TCP协议必须使用SOCK_STREAM,UDP必须使用SOCK_DGRAM

protocol:本次通信所使用的协议;IPPROTO_IP=6 / IPPROTO_UDP=17(可以使用宏,也可以使用数字)

返回值:成功,返回一个文件描述符----操作句柄;失败,返回-1。

2、为套接字绑定地址信息:int bind(int sockfd,struct sockaddr* addr,socklen_t addrlen);

sockfd:socket() 创建套接字返回的操作句柄

addr:当前绑定的地址信息

socklen_t addrlen:地址信息长度

返回值:成功返回0;失败返回-1。

3、接收数据:ssize_t recvfrom(int sockfd,void* buf,int len,int flag,struct addr* srcaddr,socklen_t * addrlen);

ssize_t:有符号int;

size_t:无符号int

sockfd:创建套接字返回的操作句柄

buf:用于存放接收到的数据的空间地址

len:需要接受的数据长度

flag:选项标志,通常默认为0,表示阻塞接收

srcaddr:本条数据的源端地址信息

addrlen:输入输出参数,指定要接收多长的地址长度,但实际可能并没有那么长,所以还会返回实际接收到的地址长度

返回值:成功,返回实际接收到的数据长度;失败或出错返回-1。

4、发送数据:ssize_t sendto(int sockfd,void *data,int len,int flag,struct sockaddr* peeraddr,socklen_t addrlen);

sockfd:操作句柄

data:要发送的数据的首地址

len:要发送的数据长度

flag:默认为0,阻塞发送

peeraddr:对端地址信息

addrlen:地址结构长度

返回值:成功,返回实际发送的数据长度;失败,返回-1。

5、关闭套接字:int close(int fd);

fd:操作句柄

流程外的重要接口:

字节序转换接口:

unint32_t htonl(uint32_t hostlong);----32位数据主机字节序到网络字节序的转换

unint16_t htons(uint32_t hostshort);----16位数据主机字节序到网络字节序的转换

unint32_t ntohl(uint32_t netlong);----32位数据网络字节序到主机字节序的转换

unint16_t ntohs(uint32_t netshort);----16位数据网络字节序到主机字节序的转换

注意:port端口转换使用htons/ntohs,ip转换使用htonl/ntohl,不能混用。

将字符串点分十进制IP地址转换为整型网络字节序IP地址:

“192.168.2.2”————》0xc0a80202

in_addr_t inet_addr(const char* cp);

将网络字节序IP地址转换为字符串点分十进制IP地址:

0xc0a80202————》“192.168.2.2”

char* inet_ntoa(struct in_addr in);

以上接口仅限于IPV4地址使用

不限于IPV4的地址转换:

int inet_pton(int af,const char* src,void* dst);

const char* inet_ntop(int af,void* src,char* dst,socklen_t size);

(1)这两个函数的af参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。如果,以不被支持的地址族作为af参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.

(2)第一个函数尝试转换由src指针所指向的字符串,并通过dst指针存放二进制结果,若成功则返回值为1,否则如果所指定的af而言输入字符串不是有效的表达式格式,那么返回值为0.

(3)inet_ntop进行相反的转换,从数值格式(src)转换到表达式(dst)。inet_ntop函数的src参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。size参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果size太小,不足以容纳表达式结果,那么返回一个空指针,并置为errno为ENOSPC。

服务端代码

#include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<string.h>#include<arpa/inet.h>//字节序转换接口头文件#include<netinet/in.h>//地址结构/协议类型头文件#include<sys/socket.h>//套接字接口文件int main(){//1.创建套接字//int socket(地址域类型,套接字类型,协议类型);int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);if(sockfd<0){perror("socket error:");return -1;}//2.为套接字绑定地址信息//int bind(操作句柄,地址结构信息,地址长度);struct sockaddr_in addr;//定义ipv4地址结构addr.sin_family=AF_INET;addr.sin_port=htons(9000);//设置端口addr.sin_addr.s_addr=inet_addr("192.168.85.128");int len=sizeof(addr);int ret=bind(sockfd,(struct sockaddr*)&addr,len);if(ret<0){perror("bind error:");return -1;}while(1){//3.接收数据//recvfrom(句柄,空间,长度,标志,对端地址,地址长度)char buf[1024]={0};struct sockaddr_in paddr;int len=sizeof(struct sockaddr_in);ret= recvfrom(sockfd,buf,1023,0,(struct sockaddr*)&paddr,&len);if(ret<0){perror("recv error:");return -1;}uint16_t cport=ntohs(paddr.sin_port);char* cip=inet_ntoa(paddr.sin_addr);printf("client-[%s:%d]client say:%s\n",cip,cport,buf);//4.回复数据memset(buf,0x00,1024);printf("server say:");fflush(stdout);fgets(buf,1023,stdin);ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&paddr,len);if(ret<0){perror("send error:");return -1;}}//5.关闭套接字close(sockfd);return 0;}

客户端代码

头文件:

/** 封装一个udp_socket类* 通过实例化的对象调用对应的成员接口实现udp客户端及服务端搭建*/ #include<iostream>#include<string>#include<unistd.h>#include<arpa/inet.h>#include<netinet/in.h>#include<sys/socket.h>#include<cstdio>using namespace std;class UdpSocket{private:int _sockfd;public:UdpSocket():_sockfd(-1){}bool Socket(){_sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);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;ret=bind(_sockfd,(struct sockaddr*)&addr,len);if(ret<0){perror("bind error:");return false;}return true;}bool Send(std::string &data,const std::string &ip,int 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=sendto(_sockfd,data.c_str(),data.size(),0,(struct sockaddr*)&addr,len);if(ret<0){perror("sendto error:");return false;}return true;}bool Recv(std::string *buf,std::string *ip=NULL,int *port=NULL){struct sockaddr_in addr;socklen_t len=sizeof(struct sockaddr_in);char tmp[4096]={0};int ret=recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)&addr,&len);if(ret<0){perror("recvfrom error:");return false;}buf->assign(tmp,ret); //申请ret长度的空间,并且将tmp的数据拷贝过去if(ip!=NULL){*ip=inet_ntoa(addr.sin_addr);}if(port!=NULL){*port=ntohs(addr.sin_port);}return true;}bool Close(){if(_sockfd!=-1){close(_sockfd);}return true;}};

主程序

#include"udp_socket.hpp"#define CHECK_RET(q) if((q)==false){return -1;}int main(){UdpSocket sock;//1.创建套接字CHECK_RET(sock.Socket());//2.绑定地址信息(客户端不推荐执行这一操作)while(1){//3.发送数据std::cout<<"client say:";std::string buf;std::cin>>buf;CHECK_RET(sock.Send(buf,"192.168.85.128",9000));//4.接收数据buf.clear();CHECK_RET(sock.Recv(&buf));std::cout<<"server say:"<<buf<<endl;}//5.关闭套接字sock.Close();return 0;}

输出结果:

TCP通信程序

通信流程:

创建连接的 原理:

服务端创建一个套接字 S1(包含sip+sport+tcp) ,并将其置于listen状态,开始处理客户端的请求(S1仅用于接收新的客户端连接);客户端向服务端发送一个连接请求,服务端通过复制 S1 为该客户端创建一个新的套接字(包含sip+sport+dip+dport+tcp);

接口:

1、创建套接字:int socket(int domain,int type,int protocol);

2、绑定地址信息:int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

3、开始监听:int listen(int sockfd,int backlog);

sockfd:套接字描述符

backlog:服务端在同一时间能够处理的最大连接数

内核中有一个已完成连接队列----存储已经完成的客户端连接,如果这个队列已满,操作系统将不能再接收新的连接请求。

已完成连接队列的大小=backlog+1;

syn泛洪攻击:向服务端大量发送连接请求,阻碍服务端正常运转;解决办法:已完成连接队列,防火墙

4、客户端发送连接请求:int connect(int sockfd,struct sockaddr*srvaddr,socklen_t len);

sockfd: 套接字描述符

srvaddr: 服务端地址信息

len: 地址长度

返回值:成功返回0;失败返回-1。

5、服务端获取新建连接;int accept(int s, struct sockaddr *cliaddr, socklen_t *addrlen);

sockfd:监听套接字----服务端最早创建的,只用于获取新链接的套接字

cliaddr: 新的连接的客户端地址信息

addrlen:输入输出参数,指定地址信息长度,以及返回实际长度

返回值:新建连接的描述符----往后与客户端的通信都通过这个描述符完成。

6、收发数据:tcp通信因为socket中含有完整的五元组,所以收发数据数据都不需要指定地址

ssize_t send(int sockfd, void *data, int len, int flag);

sockfd: 描述符

data:要发送的数据

len:数据长度

flag:0----默认阻塞

返回值:成功返回实际发送的长度;失败返回-1;连接断开会触发异常

ssize_t recv(inst sockfd,void *buf,int len,int flag);

sockfd:描述符

buf:存放接收到的数据的空间地址

flag:0-----阻塞接收

返回值:成功返回实际收到的数据长度;出错返回-1;连接断开返回0.

7、关闭套接字:int close(int sockfd);

代码实例:

封装的socket类:

```cpp#include<cstdio>#include<unistd.h>#include<iostream>#include<string>#include<arpa/inet.h>#include<netinet/in.h>#include<sys/socket.h>#define CHECK_RET(q) if((q)==false){return -1;}#define LISTEN_BACKLOG 5class TcpSocket{private:int _sockfd;public:TcpSocket():_sockfd(-1){}bool Socket(){_sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(_sockfd<0){perror("socket error:");return false;}return true;}bool Bind(const std::string &ip,const 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[0]);socklen_t len=sizeof(sockaddr);int ret=bind(_sockfd,(sockaddr*)&addr,len);if(ret<0){perror("bind error:");return false;}return true;}bool Listen(int backlog = LISTEN_BACKLOG){//listen(描述符,同一时间最大连接数);int ret=listen(_sockfd,backlog);if(ret<0){perror("listen error");return false;}return true;}bool Connect(const std::string &ip,const 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[0]);socklen_t len=sizeof(sockaddr);int ret=connect(_sockfd,(sockaddr*)&addr,len);if(ret<0){perror("connect error");return false;}return true;}bool Accept(TcpSocket* sock,std::string *ip=NULL,uint16_t *port=NULL){//int accept(监听套接字,获取客户端地址,长度)sockaddr_in addr;socklen_t len=sizeof(sockaddr_in);int newfd=accept(_sockfd,(sockaddr*)&addr,&len);if(newfd<0){perror("accept error");return false;}sock->_sockfd=newfd;if(ip!=NULL){*ip=inet_ntoa(addr.sin_addr);}if(port!=NULL){*port=ntohs(addr.sin_port);}return true;}bool Recv(std::string *buf){//int recv(描述符,空间,数据长度,标志位)//返回值:实际h获取的大小 0---连接断开;-1------出错了char tmp[4096]={0};int ret=recv(_sockfd,tmp,4096,0);if(ret<0){perror("recv error");return false;}else if(ret==0){printf("连接断开!\n");return false;}buf->assign(tmp,ret);return true;}bool Send(const std::string &data){//int send(描述符,数据,长度,标志位);int total=0;while(total<data.size()){int ret=send(_sockfd,&data[0]+total,data.size()-total,0);if(ret<0){perror("send error");return false;}total+=ret;}return true;}bool Close(){if(_sockfd!=-1){close(_sockfd);}return true;} };

服务端代码

#include "tcpsocket.hpp"int main(int argc,char* argv[]){//命令行: ./tcp_srv 192.168.2.2 9000if(argc!=3){printf("格式:./tcp_src 192.168.2.2 9000\n");return -1;}std::string srvip=argv[1];uint16_t srvport=std::stoi(argv[2]);TcpSocket lst_sock;//监听套接字,仅用于接受新连接//1.创建套接字CHECK_RET(lst_sock.Socket());//2.绑定地址信息CHECK_RET(lst_sock.Bind(srvip,srvport));//3.开始监听CHECK_RET(lst_sock.Listen());while(1){//4.获取新连接TcpSocket cli_sock;//客户端套接字std::string cli_ip;//客户端ip地址uint16_t cli_port; //客户端端口bool ret=lst_sock.Accept(&cli_sock,&cli_ip,&cli_port);if(ret==false){//客户端发生错误,服务端不能退出,继续处理下一个客户端的请求continue;}std::cout<<"获取新建连接:"<<cli_ip<<":"<<cli_port<<std::endl;//5.收发数据---使用获取的新建套接字进行通信std::string buf;ret=cli_sock.Recv(&buf);if(ret==false){cli_sock.Close();continue;}std::cout<<"client say:"<<buf<<std::endl;buf.clear();std::cout<<"server say:";std::cin>>buf;ret=cli_sock.Send(buf);if(ret==false){cli_sock.Close();}}//6.关闭套接字lst_sock.Close();return 0;}

客户端代码

#include "tcpsocket.hpp"int main(int argc, char *argv[]){//通过参数传入要连接的服务端的地址信息if (argc != 3) {printf("输入格式: ./tcp_cli srvip srvport\n");return -1;}std::string srvip = argv[1];uint16_t srvport = std::stoi(argv[2]);TcpSocket cli_sock;//1. 创建套接字CHECK_RET(cli_sock.Socket());//2. 绑定地址信息(不推荐)//3. 向服务端发起连接CHECK_RET(cli_sock.Connect(srvip, srvport));while(1){//4. 收发数据std::string buf;std::cout<<"client say:";std::cin>>buf;CHECK_RET(cli_sock.Send(buf)); //发送数据buf.clear();CHECK_RET(cli_sock.Recv(&buf)); //接收数据std::cout<<"server say:"<<buf<<std::endl;}//5. 关闭套接字CHECK_RET(cli_sock.Close());return 0;}

出现问题:

上述代码虽能正常运行,但同时也存在很大的问题:

accept、recv和send都是阻塞接口,任意一个接口的调用都有可能会导致服务端流程阻塞

本质原因:当前的服务端不知道什么时候又新连接到来,什么时候哪个客户端有数据到来,因此流程只能固定的去调用接口,但是这种调用方式可能会造成阻塞

解决方案:

多执行流并发处理----为每个客户端创建一个执行流负责这个客户端的通信

好处:

即使主线程被卡在获取新连接这一步,也不会影响其他执行流中客户端的通信某个客户端阻塞,不会影响主线程和其他线程

具体操作:在主线程中获取新建连接,一旦获取到了就创建一个执行流,通过这个新建连接与客户端进行通信

多线程:普通线程与主线程数据共享,指定入口函数执行;主线程不能随意释放套接字,因为资源共享,一旦释放,其他线程无法使用

多进程:子进程复制了父进程,但是数据独有;要注意僵尸进程的处理,注意父子进程数据独有,父进程用不到套接字资源因此创建子进程之后要记得释放掉,否则会造成资源泄露。

优化后的代码

多线程:服务端

#include "tcpsocket.hpp"#include <pthread.h>void *thr_entry(void *arg){bool ret;TcpSocket *clisock = (TcpSocket*)arg;while(1) {//5. 收发数据--使用获取的新建套接字进行通信std::string buf;ret = clisock->Recv(&buf);if (ret == false) {clisock->Close();delete clisock;return NULL;}std::cout << "client say: " << buf << std::endl;buf.clear();std::cout << "server say: ";std::cin >> buf;ret = clisock->Send(buf);if (ret == false) {clisock->Close();delete clisock;return NULL;}}clisock->Close();delete clisock;return NULL;}int main(int argc, char *argv[]){//通过程序运行参数指定服务端要绑定的地址// ./tcp_srv 192.168.2.2 9000if (argc != 3) {printf("usage: ./tcp_src 192.168.2.2 9000\n");return -1;}std::string srvip = argv[1];uint16_t srvport = std::stoi(argv[2]);TcpSocket lst_sock;//监听套接字//1. 创建套接字CHECK_RET(lst_sock.Socket());//2. 绑定地址信息CHECK_RET(lst_sock.Bind(srvip, srvport));//3. 开始监听CHECK_RET(lst_sock.Listen());while(1) {//4. 获取新建连接TcpSocket *clisock = new TcpSocket();std::string cliip;uint16_t cliport;bool ret = lst_sock.Accept(clisock, &cliip,&cliport);if (ret == false) {continue;}std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";//创建线程专门负责与指定客户端的通信pthread_t tid;pthread_create(&tid, NULL, thr_entry, (void*)clisock);pthread_detach(tid);}//6. 关闭套接字lst_sock.Close();return 0;}

多进程:服务端

#include "tcpsocket.hpp"#include <signal.h>#include <sys/wait.h>void sigcb(int no){while(waitpid(-1, NULL, WNOHANG) > 0);}void worker(TcpSocket &clisock){//child processbool ret;while(1) {//5. 收发数据--使用获取的新建套接字进行通信std::string buf;ret = clisock.Recv(&buf);if (ret == false) {clisock.Close();exit(0);}std::cout <<"client say: "<<buf<<std::endl;buf.clear();std::cout << "server say: ";std::cin >> buf;ret = clisock.Send(buf);if (ret == false) {clisock.Close();exit(0);}}clisock.Close();//释放的是子进程的clisockexit(0);return;}int main(int argc, char *argv[]){//通过程序运行参数指定服务端要绑定的地址// ./tcp_srv 192.168.2.2 9000if (argc != 3) {printf("usage: ./tcp_src 192.168.2.2 9000\n");return -1;}signal(SIGCHLD, SIG_IGN);//signal(SIGCHLD, sigcb);std::string srvip = argv[1];uint16_t srvport = std::stoi(argv[2]);TcpSocket lst_sock;//监听套接字//1. 创建套接字CHECK_RET(lst_sock.Socket());//2. 绑定地址信息CHECK_RET(lst_sock.Bind(srvip, srvport));//3. 开始监听CHECK_RET(lst_sock.Listen());while(1) {//4. 获取新建连接TcpSocket clisock;std::string cliip;uint16_t cliport;bool ret = lst_sock.Accept(&clisock, &cliip,&cliport);if (ret == false) {continue;}std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";pid_t pid = fork();if (pid < 0) {clisock.Close();continue;}else if (pid == 0) {worker(clisock);}//父子进程数据独有,父进程关闭不会对子进程造成影响clisock.Close();//释放的是父进程中的clisock}//6. 关闭套接字lst_sock.Close();return 0;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。