文章目录
- socket编程接口
-
- [认识`struct sockaddr`类](#认识
struct sockaddr
类)
- [认识`struct sockaddr`类](#认识
- 编写一个server服务
- Client代码
- 查看启动结果
- 代码修正
- 不同机器之间的通信
- 对方发的命令也进行执行或者是打印
- [udp tcp是支持全双工的, 所以我们可以实现一个聊天室](#udp tcp是支持全双工的, 所以我们可以实现一个聊天室)
socket编程接口
socket常见接口API:
认识struct sockaddr
类
网络套接字编程时,socket也分很多类:
1.unix socket:域间socket->用的是同一套接口, 不过用的是同一台机器上的文件路径,类似命名管道,负责本主机内部进行通信
2.网络socket:ip+port 网络通信(也可实现本地socket)(重点)
3.原始socket(应用层直接访问数据链路层,一般用于编写一些网络工具)
设计者想实现在不同应用场景使用一套接口,struct sockaddr{};是一个通用的地址类型
由于历史原因, 当初设计的时候不支持void*的通用接口
所以使用这样的方式
编写一个server服务
实现一个服务:
封装一个udpserver
构建服务基本框架:
UdpServer.hpp
cpp
#pragma once
#include "nocopy.hpp"
#include <iostream>
//父类内不存在拷贝, 所以基类也不存在拷贝
class UdpServer : public nocopy
{
public:
UdpServer(){}
void Init(){}
void Start(){}
~UdpServer(){}
private:
};
cpp
#include "UdpServer.hpp"
#include <memory>
int main()
{
//使用std::make_unique函数初始化它
std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();
usvr->Init();
usvr->Start();
return 0;
}
为了防止这个类服务被拷贝, 所以禁用一系列的拷贝函数
nocopy.hpp
cpp
#pragma once
#include <iostream>
class nocopy
{
public:
nocopy() = default;
nocopy(const nocopy&) = delete;
const nocopy& operator=(const nocopy&) = delete;
//推荐禁用上面那个
//nocopy& operator=(const nocopy&) = delete;
~nocopy() = default;
};
socket编写:
返回值:
创建成功返回一个文件描述符,出错返回-1
参数3:
确认是udp还是TCP
udp这里是固定写法
创建套接字
绑定网络信息
![](https://i-blog.csdnimg.cn/direct/58cc839f7db34673a0c70342765ea922.png)
返回值: 成功为0 , 错误返回-1, 错误码被设置
参数2虽然是struct sockaddr但是我们要使用网络套接字中的struct sockaddr_in:
![](https://i-blog.csdnimg.cn/direct/90df1ca14e7c4ad1adbfc9d8282915da.png)
首先这个填充字段指的是:
in_addr类是指
而
是指
##表示拼接
将传入的参数与##后面的符号进行拼接
所以这个结构体内的宏代表的参数是sin_family
cpp
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 指定的内存大小清零, 头文件是<strings.h>
// 也可以使用memset
// 告诉系统绑定网络通信的信息
local.sin_family = AF_INET; // 协议家族,s表示socket的意思
// in表示inet, 使用ipconfig查看会发现inet表示IP地址,
// 这的in也表示ip地址, 与上文的创建socket的参数1不同, 这边只是绑定信息
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip.c_str());
//我们自己写的是一个主机序列, 要转化为网络序列htons();
//主机转网络短整型, 同样在结构体内的是类型匹配的
![](https://i-blog.csdnimg.cn/direct/1466e616efb44661ba8eea8cfa0f780c.png)
对于我们的网络IP, 同样, 我们想要1. 4字节IP 2. 转成网络序列
将输入的字符串IP转化为点分十进制IP,并返回
结构体填完, 只是在当前的用户地址空间, 但是还没有设置到内核, 需要调用bind函数
udp服务器接发消息, 它不面向连接:
收消息:
发消息
至此已完成, 运行查看:
怎么验证:
![](https://i-blog.csdnimg.cn/direct/892b23eb41cb4e87b347199bd9c55ece.png)
n:number数字
a:all 所有的
u:udp
p:process进程
![](https://i-blog.csdnimg.cn/direct/1bc7759acc6440bb909f4b9eab1048d7.png)
Client代码
创建套接字
客户端信息输入:
发送信息
![](https://i-blog.csdnimg.cn/direct/4ce265a8821b484da0311e65c5f3d1a3.png)
接受消息
查看启动结果
信息查看
带上-n选项
127.0.0.1: 本地环回地址, 通常用于进行网路cs(client server)的测试
cs都在一台机器, 用于测试将信息自己发给自己是否正确
带上-p选项
输出的信息
客户端的端口号和ip自动变成了
客户端的首次运行, 在没有发消息时, 看不到client的相关网络信息:
只有在client发送一条信息之后, 才会看到client的相关信息
说明, 客户端bind是在首次发送数据的时候, 进行由内核随机bind
代码修正
1.获取内核分配给客户端的信息
在server模块中, client首次发送的消息会被recvfrom函数接受, 那么这个函数的后两个参数就是表示这个客户端的信息
所以, 打印需要的信息
再次运行查看:
127.0.0.1: 本地环回地址, 通常用于进行网路cs(client server)的测试
cs都在一台机器, 用于测试将信息自己发给自己是否正确
为了封装性, 将客户端信息进行封装:
![](https://i-blog.csdnimg.cn/direct/c3a520e052214c6ab899258e0c436b7e.png)
![](https://i-blog.csdnimg.cn/direct/a5fa84bb09be419bbc98dceefb62cfec.png)
本地测试完成
但是我们的服务器ip绑定只能是127.0.0.1本机, 如果是实际的本地机器, 本地ip也可以, 云服务器的ip不行, 因为云服务器的IP是提供商虚拟出来的公网Ip, 不能直接进行bind
当然, 也不推荐给服务器绑定固定的IP, 使用随机ip更合适, 这样就能实现IP的动态绑定, 不然, 只能绑定的固定的ip机器进行服务器的访问
2.修正不匹配ip不能访问的问题
![](https://i-blog.csdnimg.cn/direct/e2c01a511b6e4013beaf702a222c5290.png)
固定ip才能访问
对应的main.cc文件内以及udpserver.hpp文件的所有的server的ip都要去除, 这边就不再演示
不同机器之间的通信
利用xftp将udp_client传给wsl的ubuntu机器进行演示
传过去默认没有可执行 chmod +x udp_client即可
这边演示两台机器上的相互通信, 原机是虚拟机ubuntu机器
现在在windows下使用wsl创建一台ubuntu机器, 两台机器ip不同
都是虚拟出来的ip
![](https://i-blog.csdnimg.cn/direct/a8d0c7ae6bd84dd0add8bbb0a3728945.png)
现在模拟在windows下的udp_client代码:
可以看到win和linux进行了通信
只不过win的汉字编码和Linux汉字的编码方式不同, 输入过去的汉字不能正确的回显, 这边只需要修改win的终端编码方式即可,这边就不再演示
对方发的命令也进行执行或者是打印
可以使用exec*系列进行使用, 不过这边利用popen函数
做两件事:
1.创建管道
2.识别字符指令, fork创建子进程并执行
可以利用FILE的文件指针, 返回command的结果
type可以利用r w r+等方式来进行处理我们的这个FILE
最后使用pclose进行关闭
打开失败就返回nullptr
![](https://i-blog.csdnimg.cn/direct/ffa34a060fe943ccb1519baddf3ea28e.png)
udp tcp是支持全双工的, 所以我们可以实现一个聊天室
udp+线程池实现聊天室:
在线程池中, 任务现在是一个函数方法
在server启动的时候
首先进行, 将远端的信息接收进buf中,同时被填充的peer结构体中包含对应机器的port和ip
然后判断就收消息成功之后, 添加用户和用户输入的信息获取, 然后进行message的处理方法Route与当前对象, 当前客户端的信息, 当前的message绑定为task, 然后将这个task添加到线程池中
![](https://i-blog.csdnimg.cn/direct/e8f3977127ae4432bbe4371c2e64e4f3.png)
其中:
添加用户操作为看他是否在当前的用户列表中,不在,就添加
收到的message消息的处理方式是
将给定的消息message通过套接字sock发送给所有在线用户
然后在线程池中, 进行阻塞队列式的执行访问
然后对client进行多线程修改, 一个线程专门用于收消息, 一个线程专用于发消息, 主线程用于操作这个整体流程
整体没变, 实现模块化的划分:
聊天室实现成功, 但是输出和输入会混在一起. 再次修改
在linux中, 每个 终端是在/dev/pts 目录下有存在, 直接在这边输出信息也行, 方法有很多, 这边可以使用向/dev 输出的操作, 我们使用另一种方式
在输出消息的时候, 代码部分是向cerr打印, 那么我们可以利用管道, 在终端使用重定向实现输入输出的分别实现
1.创建管道
2.重定向输出
server端不变
演示
现在在回回显处进行修改代码, 以便确认身份
主要是在message进行修改,
然后输出的时候, 直接输出就行
结果演示:
就此, udp_socket完成