应用层简单实现udp / tcp网络通信

2024-09-07 20:04

本文主要是介绍应用层简单实现udp / tcp网络通信,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、常见网络接口总结

1、创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

int socket(int domain, int type, int protocol);

domain:AF_INET:网络通信,AF_LOCAL:本地通信

type:UDP:SOCK_DGRAM,TCP:SOCK_STREAM

protocol:协议编号一开始设0

返回值:文件描述符,Linux下一切皆文件

2、绑定端口号 (TCP/UDP, 服务器)

int bind(int socket, const struct sockaddr *address, socklen_t address_len);

socket:创建socket文件描述符的返回值

address:输入型参数,传递一个带有服务器信息(IP, 端口号, AF_INET)的结构体对象,具体详见http://t.csdnimg.cn/aY2O0

address_len:结构体 address 大小

3、开始监听socket (TCP, 服务器)

int listen(int socket, int backlog);

将套接字设为监听状态,随时准备别人链接服务器

backlog:表示大小,设为8

4、接收请求 (TCP, 服务器)

int accept(int socket, struct sockaddr* address, socklen_t* address_len);

服务器监听状态之后,接受客户端链接

address:输出型参数,得到客户端的信息

address_len:输出型参数,address大小

返回值:文件描述符

对比 accept 函数返回值和 socket

就好比一家餐馆,socket 是门口拉客的,一般叫 listen_sockfd 监听套接字。返回值是客人进餐馆接待客人的服务员,为用户提供网络IO的服务套接字。所以之后真正开始服务的函数用的是accept函数返回值

5、建立连接 (TCP, 客户端)

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

客户端在有需要时进行链接服务器

address:输入型参数,传入带有服务器信息的结构体,表示我要链接你这个服务器。

address_len:结构体 address 大小

6、把字符串ip转化成网络序列ip

in_addr_t inet_addr(const char *cp);(不安全)
int inet_pton(int af, const char *src, void *dst);(推荐)

dst:直接填 struct sockaddr_in 中的 sin_addr

7、把网络序列ip转化成字符串ip

char *inet_ntoa(struct in_addr in);(不安全)内部只维护一块静态空间存储IP,会导致覆盖问题

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);(推荐)
af:AF_INET:网络通信,AF_LOCAL:本地通信

src:4字节网络序列IP

dst:传入自己定义的缓冲区存放ip

size:缓冲区大小

8、从网络中收数据

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

用于tcp读取数据

flag:设置0,阻塞读取

由于tcp有链接,且sockfd本质是文件描述符,可以用read函数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

用于udp读取数据

src_addr:输出型参数,客户端信息

addrlen:输出型参数,src_addr大小

9、在网络中发数据

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

用于tcp发数据

flag:设置0,阻塞发送

由于tcp有链接,且sockfd本质是文件描述符,可以用write函数

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

用于udp发数据

dest_addr:输入型参数,服务器信息

addrlen:dest_addr大小

10、库函数做网络字节序和主机字节序的转换接口

二、udp_echo_server

1、服务器初始化服务

    void InitServer(){// 1.创建socket文件描述符 一般是3_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0){LOG(FATAL, "socket error\n");exit(SOCKET_ERROR); }LOG(DEBUG, "socket create success, _sockfd = %d\n", _sockfd);// 2.先填充跨网络地址信息struct sockaddr_in local;memset(&local, 0, sizeof local);// 通信方式local.sin_family = AF_INET;// 主机转成网络序列local.sin_port = htons(_port);// 要4字节ip 要网络序列ip// local.sin_addr.s_addr = inet_addr(_localip.c_str());// 服务器端进行任意IP绑定local.sin_addr.s_addr = INADDR_ANY;// 3.绑定端口号 把地址信息绑定进套接字int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof local);if(n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR); }LOG(DEBUG, "bind success\n");}

(1)创建sockfd文件描述符

(2)为了绑定函数,初始化 struct socket_in server,并填入服务器信息

注意:服务器一定要绑定 INADDR_ANY 任意IP,这样才能接收到全部IP地址主机的数据

(3)绑定端口号以及一系列服务器信息

2、服务器启动服务

    // 绑定成功就开始服务void Start(){_isrunning = true;char inbuffer[1024];// 死循环while(_isrunning){struct sockaddr_in peer;socklen_t len = sizeof peer;// 服务器收客户端消息ssize_t n = recvfrom(_sockfd, inbuffer, sizeof inbuffer - 1, 0, (struct sockaddr*)&peer, &len);if(n > 0){// 获取哪一个客户端的端口号和IP地址InetAddr addr(peer);inbuffer[n] = 0;cout << "[" << addr.Ip() << ":" << addr.Port() << "]#" << inbuffer << endl;string echo_string = "[udp_server echo] #";echo_string += inbuffer;// 服务器发给客户端消息sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr*)&peer, len);}}}

至少服务器要一直死循环工作

3、客户端初始化

    // 1.获取服务器IP和端口号string server_ip = argv[1];uint16_t server_port = stoi(argv[2]);// 2.绑定服务器// 绑定// 客户端端口号一般不让用户自己设定,让客户端OS随机选择端口// 客户端一定需要绑定自己的IP地址和端口号,但不是显示绑定// 客户端在首次向服务器发送数据时,OS自动给客户端绑定IP和端口号struct sockaddr_in server;memset(&server, 0, sizeof server);server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){cerr << "create socket error" << endl;exit(1);}

(1)获取要连接的服务器信息

(2)初始化 struct socket_in server,并填入服务器信息

(3)创建套接字

(4)重点

客户端不需要显示绑定端口号,也不能自己绑定。但是不代表客户端不要绑定,在第一次客户端给服务器发数据时让客户端的OS随机绑定。

为什么不让用户自己绑定?
服务器可以自己绑定是因为服务器一般只运行一个进程服务,端口号不会冲突,但是客户端不一样,用户的操作系统上会有多个客户端,如果让用户自己设置就很可能端口号冲突,导致用户不能同时启动两个端口号一样的客户端。

4、客户端链接服务器

    while(1){//发送的数据string line;cout << "Please Enter# : ";getline(cin, line);int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof server);if(n > 0){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int m = recvfrom(sockfd, buffer, sizeof buffer - 1, 0, (struct sockaddr*)&temp, &len);if(m > 0){buffer[m] = 0;cout << buffer << endl;}elsebreak;}elsebreak;}

三、tcp_echo_server

1、服务器初始化服务

    void InitServer(){// 1.创建socket_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if(_listensockfd < 0){LOG(FATAL, "socket create error\n");exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _listensockfd);struct sockaddr_in local;memset(&local, 0, sizeof local);local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;// 2.绑定if(::bind(_listensockfd, (struct sockaddr*)&local, sizeof local) < 0){LOG(FATAL, "socket bind error\n");exit(BIND_ERROR);}LOG(INFO, "bind success\n");// 3.因为tcp是面向连接的,tcp需要不断能获取链接状态,设置为listen状态if(::listen(_listensockfd, gblcklog) < 0){LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(INFO, "listen success\n");}

(1)创建sockfd文件描述符

(2)为了绑定函数,初始化 struct socket_in server,并填入服务器信息

注意:服务器一定要绑定 INADDR_ANY 任意IP,这样才能接收到全部IP地址主机的数据

(3)绑定端口号以及一系列服务器信息

(4)开始监听

2、服务器启动服务

    // 内部类 为了拿到新线程要用的sockfd和this指针调用Service函数class ThreadData{public:int _sockfd;TcpServer* _self;InetAddr _addr;public:ThreadData(int sockfd, TcpServer* p, const InetAddr &addr):_sockfd(sockfd),_self(p),_addr(addr){}};void Loop(){// 推荐做法 忽略子进程退出信息// signal(SIGCHLD, SIG_IGN);_isrunning = true;struct sockaddr_in client;socklen_t len = sizeof client;while(_isrunning){// 4.获取链接int sockfd = accept(_listensockfd, (struct sockaddr*)&client, &len);// listensockfd获取失败if(sockfd < 0){LOG(WARNING, "accept error\n");continue;}// 获取成功InetAddr addr(client);LOG(INFO, "get a new link, client info: %s\n", addr.AddStr().c_str());// 版本一 无法并发访问服务器// Service(sockfd, addr);// 版本二 多进程版并发访问服务器// pid_t id = fork();// if(id == 0)// {//     // 继承下来的父进程的_listensockfd(3)需要在子进程关闭,子进程使用sockfd(4)//     ::close(_listensockfd);//     // 创建孙子进程后,子进程退出,此时孙子进程成为孤儿进程,执行服务,死活只由OS关心//     if(fork() > 0)//     {//         exit(0);//     }//     Service(sockfd, addr);//     exit(0);// }// // 同理父进程也要关闭sockfd(4)因为那是属于子进程的,用_listensockfd(3)// // 必须要释放子进程的sockfd, 防止文件描述符泄漏 // int n = waitpid(id, nullptr, 0);// if(n > 0)// {//     LOG(INFO, "wait chid success\n");// }// 版本三 多线程版并发访问服务器// pthread_t tid;// ThreadData* td = new ThreadData(sockfd, this, addr);// // 新线程内部分离线程// pthread_create(&tid, nullptr, Excute, td);// 版本四 线程池版本task_t t = bind(&TcpServer::Service, this, sockfd, addr);ThreadPool<task_t>::GetInstance()->Equeue(t);}_isrunning = false;}// 类里面方法带有this指针,标为静态// 多线程因为共用同一个文件描述符表,所以不能关闭static void* Excute(void* args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->_self->Service(td->_sockfd, td->_addr);delete(td);return nullptr;}void Service(int sockfd, InetAddr addr){while(1){char inbuffer[1024];ssize_t n = read(sockfd, inbuffer, sizeof inbuffer - 1);if(n > 0){inbuffer[n] = 0;string echo_string = "[server echo] #";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if(n == 0){LOG(INFO, "client %s quit\n", addr.AddStr().c_str());break;}else{LOG(ERROR, "read error: %s\n", addr.AddStr().c_str());break;}}::close(sockfd);}

(1)accpet 函数循环获取新连接,获取成功填入客户端信息

(2)服务端提供服务的四种方式

a、直接调用服务函数

不能并发访问服务器,不使用

b、多进程调用服务函数

创建子进程后,父进程关闭子进程套接字 sockfd,子进程关闭父进程套接字 _listen_sockfd,让父子进程不能相互影响。

signal(SIGCHLD, SIG_IGN); 忽略子进程退出,父进程不用阻塞等待。

也可以在子进程中创建孙子进程,子进程退出,孙子进程变成孤儿进程执行服务函数,函数结束不用父进程管,让OS管。还是推荐忽略子进程退出的方式。

c、多线程调用服务函数

为了线程创建函数中的参数线程调用函数不能有this指针,把调用函数设 static

多线程使用同一张文件描述符表,不能关闭任何套接字

要定义内部类,给线程执行函数传入 sockfd, this指针(调用Service函数), addr

新线程在执行函数中要 pthread_detach(pthread_self()) 进行线程分离

d、线程池调用服务函数

using task_t = function<void()>;

task_t t = bind(&TcpServer::Service, this, sockfd, addr);

ThreadPool<task_t>::GetInstance()->Equeue(t);

3、客户端初始化

    if (argc != 3){cerr << "Usage: " << argv[0] << " server_ip server_port" << endl;exit(0);}string server_ip = argv[1];uint16_t server_port = stoi(argv[2]);// 1.创建socketint sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){cerr << "create socket error" << endl;exit(1);}// 注意:客户端不需要显示绑定,但一定要有自己的ip和port,OS会自动绑定sockfd, 用自己的ip和随机端口号// 向服务器发送链接(第一次发送链接时,OS自动绑定)struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(server_port);::inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);

(1)获取要连接的服务器信息

(2)初始化 struct socket_in server,并填入服务器信息

(3)创建套接字

4、客户端链接服务器

    int n = ::connect(sockfd, (struct sockaddr*)&server, sizeof server);if(n < 0){cerr << "connect error" << endl;exit(2);}while(1){string message;cout << "Enter #";getline(cin, message);write(sockfd, message.c_str(), message.size());char echo_buffer[1024];n = read(sockfd, echo_buffer, sizeof echo_buffer);if(n > 0){echo_buffer[n] = 0;cout << echo_buffer << endl;}else{break; }}

这篇关于应用层简单实现udp / tcp网络通信的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1146022

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount