聊聊【Linux】Socket编程UDP Echo 服务器→翻译服务器→多人聊天室

今天翻到一篇不错的技术分享,看完之后自己也琢磨了一下,把思路梳理记录下来。

文章目录

动手通信(Start)服务端源码-V1 UdpClient 测试代码注意事项完整源码 二、V2 版本 - DictionaryServer 三、V3 版本 - ChatSystemDemo四、windows和linux之间进行通信

一、V1 版本 - echo server

UdpServer

服务端先从网络中读消息之后再写。

初始化套接字(Init)

系统调用socket

创建套接字的系统调用:

参数一:
协议族/域,用来告诉套接字是做本地通信还是网络通信还是其他。

参数二:
套接字类型:

参数三: 默认为0即可。

返回值:

我们能看到返回值竟然是一个文件描述符,所以这正是linux下一切皆文件的体现,网卡本质也是文件,未来网络通信,本质也是文件通信!

现在我们调用socket只是把网络在文件系统中打开了,还需要给文件绑定ip地址和端口号,这就要用到下面的bind。

bind

套接字编程中,bind 是将【IP 地址 + 端口号】绑定到 套接字(socket)文件描述符 上,而不是直接绑定到进程。

在介绍bind之前小编先介绍几个接下来几个常用的结构体:
struct sockaddr 通用地址结构 函数参数通用类型
struct sockaddr_in IPv4 地址结构 实际存放 IP + 端口
struct sockaddr_in6 IPv6 地址结构 IPv6 使用

第一个参数传递socket的返回值。
第二个参数传递一个填充好ip地址和端口号的结构体:

第三个参数传递结构体的大小。
返回值等于0表示绑定成功,不等于0表示失败。

在介绍bind代码代码达成之前小编先介绍两个需要包的头文件:<netinet/in.h>头文件能提供各种数据类型(sockaddr_in就在该头文件中),<arpa/inet.h>能提供各种大小端转化的方法,加上之前介绍的<sys/types.h>和<sys/socket.h>,这四个头文件是我们网络编程时几乎务必包含的四个头文件。

bind代码分两步达成:
1、填充ip和port
在填充ip和port之前,还需要在 struct sockaddr_in 结构体中,将sin_family 字段赋值为 AF_INET,用来告诉操作系统:这个套接字使用 IPv4 地址格式来进行网络通信,后续的 IP 地址和端口号都要按照 IPv4 协议来解析和处理。
首先要创建一个sockaddr_in类型结构体,之后把它的内存空间清零,我们可以用memset,小编介绍一种新方法,用bzero,需要包<strings.h>头文件,它可以将一段指定的内存空间清零。
ip和端口号需要通过命令行参数传递,所以udpserver.cc中的main函数要接受命令行参数,之后创建UdpServer对象时再把接收到的ip和端口号以参数的形式传递。

填入端口号_port时要把小端转大段端htons(_port),主机字节序转网络字节序),因为端口号也要随着报文一起发给对方主机,这样对方主机在回消息时才知道发送方在哪里。(htons中的s表示short,将16 位无符号短整型 从主机字节序转换为网络字节序)
填入ip地址要把可读性好的字符串风格的点分十进制ip转化为4个字节的整数ip,方便网络传输,这里需要用到inet_addr,它不仅可以将字符串风格的点分十进制ip转为整数ip,并且它的返回值已经是网络字节序,所以不需要再通过htons转换。
还需要注意填ip地址时要这样:local.sin_addr.s_addr,因为sin_addr是结构体,C++规定结构体不能整体赋值,所以只能对sin_addr内部的整型变量s_addr赋值。

2、sockaddr_in和socketfd进行bind
调用bind系统调用即可。

动手通信(Start)

目前服务器已经初始化完成,接下来我们就要基于文件描述符来进行通信了。

1、读取数据
因为udp不是面向字节流的,所以不能直接用read,udp有一套自己的读取、发送数据接口:

recvfrom:

参数:
1、接收方文件描述符。
2、输出型参数,用户自定义一段缓冲区,将读到的数据带出。
3、表示参数2用户自定义缓冲区的大小。
4、表示读取策略,默认置为0,表示阻塞读取。
5、输出型参数,是对端主机的ip和端口号信息,是一个sockaddr_in类型的结构体变量,方便我们回消息。(套接字编程:发送数据 = 消息内容 + 发送主体)
6、输入输出型参数,表示参数5的结构体大小,输入表示传入的结构体大小,输出表示实际结构体大小。

返回值:
大于0表示实际读取多少字节,小于0表示读取失败。

2、发送数据
sendto:

参数: 1、发送方文件描述符。这里可以引出一个结论: udp既可以读时写,也可以写时读,支持全双工通信。 其他参数类似recvfrom,只不过第5个参数是输入型参数,要传递目标主机的sockaddr_in结构体,还要注意第六个参数是整形类型,不是recvfrom的指针类型。

服务端源码-V1

```
//UdpServer.hpp

pragma once

include

include

include

include

include

include

include

include

include "Logger.hpp"

static const int gdefaultsockfd = -1;

class UdpServer
{
public:
UdpServer(const std::string &ip, const int port)
:_ip(ip)
,_port(port)
,_sockfd(gdefaultsockfd)
{

}
void Init()
{
// 1、创建套接字fd
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd < 0)
{
//创建套接字失败
LOG(LogLevel::FATAL)


暂时整理到这里。以上都是个人理解,可能有疏漏,欢迎指正。

评论 (0)

暂无评论