linux篇【12】:网络套接字<前序>—网络基础+udp套接字
目录
一.网络基础
1.认识 "协议"
举例:
2.协议分层
(1)软件分层
(2)协议分层
3.OSI七层模型
4.TCP/IP五层(或四层)模型
5.网络和操作系统之间的关系
6.数据包的封装(封包)和解包,分用
(1)下图为数据封装,解包的过程
(2)分用
(3)示例:
(4)数据包传输通过路由器转发
7.局域网(以太网)通信的原理
(1)局城网中两台主机可以互相通信
(2)局域网通信原理
8.MAC地址和IP地址
(1)生活小例子类比MAC地址和IP地址
(2)IP地址:
(3)MAC地址:
(4)主机改变所在网络时需要修改IP地址,不可修改MAC地址
(5)IP协议的两个版本, IPv4和IPv6:
二.网络编程套接字
1.源IP地址和目的IP地址
2.端口号,套接字组成介绍
3.理解 "端口号" 和 "进程ID"(端口号的意义)
4.源端口号和目的端口号
5.TCP协议与UDP协议
(1)TCP协议
(2)UDP协议
6.网络字节序
(1)规定:网络字节序默认是大端
(2)网络和主机字节序的转换函数
三.socket套接字编程接口
socket头文件:
1.socket 常见API(套接字编程接口)
2.sockaddr结构(套接字的地址结构类型定义)
3.套接字接口
(1)创建一个套接字 socket
(2)绑定网络信息 bind
(3)把字符串风格的IP地址转为4字节地址 inet_addr ,4字节转字符串 inet_ntoa
①inet_addr
②inet_ntoa
(4)网络服务 recvfrom 与 sendto
①udp特有的 recvfrom读取套接字中的信息
②sendto 向套接字发送信息
(5)日志写法(可变参数)
Log.hpp
4.部分细节解释+代码(udp套接字)
(1)INADDR_ANY
(2)inet_addr(上面有)
(3)bzero
(4)本地通信:127.0.0.11——本地环回—代表本主机
(5)sock进loop会变成-1的问题
服务器创建dup的流程:
Makefile
udpClient.cc
udpServer.cc
5.linux上的联网通信 步骤(udp套接字)
①makefile改成静态编译
②sz udpClient 把客户端发送到桌面
③ rz -e 用户下载软件
④chmod +x udpClient 将程序转为可执行程序
⑤在linux上通信可以开始了
6.windows做客户端,linux做服务器的联网通信 步骤(udp套接字)
①makefile改成静态编译
一.网络基础
1.认识 "协议"
以寄快递为例:你和卖家沟通好,买一个鼠标,实际上快递员给你的是一个包裹,里面有鼠标,
实际上多给了我一些东西,多了一张快递单,快递单是一块数据=>快递公司和快递点,快递小哥之间的协议。为了维护协议,一定要在被传输的数据上,新增其他数据(协议数据)
举例:
HTTP协议是超文本传输协议;DNS协议为域名解析协议;FTP协议为文件传输协议;SMTP协议为电子邮件传输协议
2.协议分层
(1)软件分层
软件是可以分层的,为什么要分层?
1.软件在分层的同时,也把问题归类的
2.分层的本质:软件上解耦
3.便于工程师进行软件维护
网络本身的代码,就是层状结构!
(2)协议分层
层状结构下的网络协议,我们认为,同层协议 都可以认为自已在和对方直接通信,忽略底层细节同层之间一定都要有自己的协议。
在下面这个例子中, 我们的协议只有两层(汉语协议和电话机协议); 但是实际的网络通信会更加复杂, 需要分更多的层次。分层最大的好处在于 "封装",面向对象例子。
3.OSI七层模型
4.TCP/IP五层(或四层)模型
5.网络和操作系统之间的关系
(1)体系结构直接决定, 数据包在主机内进行流动的时候,一定是要进行自顶向下(封包)或者自底向上(解包)进行流动的。以前的所有的IO都是这样的。
(2)tcp/ip协议和操作系统之间的关系是:操作系统内部,有一个模块,就叫做tcp/ip协议(传输层和网络层),网络协议栈是隶属于OS的。
(3)同层协议都认为自已在和对方直接通信——所以每一层都要有自己的协议
(4)重谈协议——计算机的视角,如何看待协议:① 体现在代码逻辑上 ② 体现在数据上
以寄快递为例:你和卖家沟通好,买一个鼠标,实际上快递员给你的是一个包裹,里面有鼠标,
实际上多给了我一些东西,多了一张快递单,快递单是一块数据=>快递公司和快递点,快递小哥之间的协议。为了维护协议,一定要在被传输的数据上,新增其他数据(协议数据)
6.数据包的封装(封包)和解包,分用
(1)下图为数据封装,解包的过程
(2)分用
有效载荷的分用过程:数据包添加报头的时候,也要考虑未来解包的时候,将自己的有效载荷交付给上层的哪一个协议!
下图为数据分用的过程:
两个结论:(大部分协议的公共属性)
1.一般而言,任何报头属性里面,一定要存在的一些字段支持,我们进行封装和解包,即:报头中一定要存着用于 区分报头和有效载荷 的数据
2.一般而言,任何报头属性里面,一定要存在的一些字段支持我们进行分用。即:报头中一定要存着用于 得知报文的有效载荷要给上层哪个协议 的数据
(3)示例:
路由器可看做一个主机同时横跨了两个局域网
所有的IP向上的协议,发送和接受主机看到的数据是一模一样的
网络 -> IP网络,IP协议屏蔽了底层网络的差异! ! !
数据“你好”从客户发出,不断封装,到以太网驱动程序完成最后封装,再通过以太网传输给路由器下的以太网驱动程序,路由器下的以太网驱动程序解包数据,传给路由器,路由器发现这个数据是要传给IPB的,再通过路由器下的以太网驱动程序封装,通过令牌环传输给目标主机所在网络,自底向上解包传输
(4)数据包传输通过路由器转发
网络传输数据的本质就是数据不断的封装和解包。路由器可看做一个主机同时横跨了两个局域网
所有的数据,必须在”网线”上跑!
7.局域网(以太网)通信的原理
(1)局城网中两台主机可以互相通信
如果两台主机,处于同一个局城网。这两台主机可以直接通信——以太网,一种局域网的标准(以太——物理学界太空中不存在的物质叫以太,为了致敬命名以太网)以太网:站在系统的角度 就是 两台主机之间的临界资源。
(2)局域网通信原理
1.每一台主机都要有唯一的标识:该主机对应的MAC地址!
2.任何一台主机,在任何时刻,都可以随时发消息——碰撞域——无法准确的听到对应的消息——识别发生了碰撞(碰撞检测)——碰撞避免——等不碰撞了过一会儿再发消息
8.MAC地址和IP地址
(1)生活小例子类比MAC地址和IP地址
1.从哪里来<源IP>,到哪里去<目的IP>——IP地址:源IP,目的IP
2.上一站从哪里来<源mac地址>,下一站要去哪里<目标mac地址>(由“到哪里去<目的IP>”决定)——MAC地址: 源mac地址,目标mac地址
MAC地址:用来在局域网中,标定主机的唯一性。
IP地址:用来在广域网(公网),标定主机的唯一性。
(2)IP地址:
(3)MAC地址:
(4)主机改变所在网络时需要修改IP地址,不可修改MAC地址
(5)IP协议的两个版本, IPv4和IPv6:
二.网络编程套接字
1.源IP地址和目的IP地址
目的IP地址:通信主机目的主机
两主机可以在同一个局域网也可以不在。
2.端口号,套接字组成介绍
我们在网络通信的时候,不止是让两台主机通信。实际上,在进行通信的时候,不仅仅要考虑两台主机间互相交互数据。本质上讲,进行数据交互的时候是用户和用户在进行交互。用户的身份,通常是用程序体现的。程序一定是在运行中——进程!
主机间在通信的本质是:在各自的主机上的两个进程在互相交互数据!
IP地址可以完成主机和主机的通信,而主机上各自的通信进程,才是发送和接受数据的一方
IP :确保主机的唯一性
端口号(port):确保该主机上某一个进程的唯一性(则一个进程只能占用一个端口号)
IP:PORT = 标识互联网中唯一的一个进程!——>这两个合起来叫 socket(套接字)(翻译是插座)
网络通信的本质:就是进程间通信! ! !
3.理解 "端口号" 和 "进程ID"(端口号的意义)
4.源端口号和目的端口号
源IP:源端口, 目的IP:目的端口——两个socket对
5.TCP协议与UDP协议
(1)TCP协议
- 传输层协议
- 有连接(要有建立连接的预备工作)
- 可靠传输(可靠性:丢包重传,数据乱序排序等,但是会做更多工作,比较复杂。使用实例:例如转账不能丢包必须用TCP协议)
- 面向字节流
(2)UDP协议
- 传输层协议
- 无连接(不需要建立连接,直接发数据)
- 不可靠传输(虽然无可靠性,但是做的工作很少,是简单协议。使用实例:例如全球直播就用UDP协议即可,网好就看,网不好就别看)
- 面向数据报
6.网络字节序
(1)规定:网络字节序默认是大端
(2)网络和主机字节序的转换函数
uint32_t htonl (uint32_ t hostlong); ——htonl(host to net 主机转网络)
三.socket套接字编程接口
socket头文件:
man socket,man htons,man inet_ addr查看所有头文件
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
1.socket 常见API(套接字编程接口)
2.sockaddr结构(套接字的地址结构类型定义)
3.套接字接口
(1)创建一个套接字 socket
man 2 socket
int socket(int domain, int type, int protocol);
domain:socket网络通信的域——网络通信 (AF_INET /PF_INET )(或 本地通信 (AF_UNIX))。现在只用AF_INET 网络通信(有的地方把AF_INET写成PF_INET也是正确的)
type:套接字类型——决定了我们通信的时候对应的报文类型(流式 / 用户数据报式)
流式套接:SOCK_STREAM ——用于TCP协议
用户数据报式套接:SOCK_DGRAM ——用于UDP协议
protocol:协议类型——网络应用中设置为 0。(因为AF_INET+SOCK_STREAM—默认是TCP套接字;AF_INET+SOCK_DGRAM—默认是UDP套接字)
返回值:成功返回文件描述符(套接字描述符),错误返回-1并设置错误码(套接字类型本质就是文件描述符)
(2)绑定网络信息 bind
man 2 bind
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd:套接字这个文件描述符。addr:传入我们自己创建的信息 struct sockaddr_in local 的地址,然后把它强转成struct sockaddr类型结构体,内部会自动识别是什么类型的套接字做绑定。addrlen:sockaddr类型结构体 的大小
返回值:成功返回0,失败返回-1
例如:if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1)
(3)把字符串风格的IP地址转为4字节地址 inet_addr ,4字节转字符串 inet_ntoa
①inet_addr
in_addr_t inet_addr(const char *cp); 把字符串风格的IP地址 cp 转为4字节地址并返回。inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n 主机字节序转网络字节序(使用后就不用再调用htonl了)注意:这类函数在转变IP风格时都会自动进行主机字节序和网络字节序之间的转换。
返回值:成功返回IP对应的网络字节序的数;失败返回INADDR_NONE;
in_addr_t就是4字节类型
②inet_ntoa
char *inet_ntoa(struct in_addr in); 把4字节IP地址转为字符串风格的IP地址并返回。
例子: std::string peerIp = inet_ntoa(peer.sin_addr); //拿到了对方的IP
inet_ntoa不是线程安全的函数
因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果。
(4)网络服务 recvfrom 与 sendto
①udp特有的 recvfrom读取套接字中的信息
man recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
从特定套接字 sockfd中读取数据到缓冲区buf中,buf大小为len,flags设为0——阻塞式读取
src_addr:(输出型参数)当服务器读取客户端发送的消息时——哪个客户端给你发的消息,就把这个客户端套接字信息存入src_addr中。(src_addr的类型是套接字类型指针struct sockaddr*,传入的网络套接字类型struct sockaddr_in*需要强转成此类型指针 struct sockaddr*。)
addrlen:(输入输出型参数)客户端这个缓冲区大小。(socklen_t就是unsigned int)
返回值:返回读到的字节数,错误就返回-1错误码被设置
当客户端使用recvfrom读取服务器返回发送的消息时——src_addr和addrlen没意义,但是还是要定义一个套接字类型结构体添上占位
void *recverAndPrint(void *args)
{while (true){int sockfd = *(int *)args;char buffer[1024];struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl;}}
}
②sendto 向套接字发送信息
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
通过客户端的指定套接字sockfd,发送buf中的数据,buf的大小是len,flags=0 默认阻塞式发送,
dest_addr:(输入型参数)向哪个主机发消息,套接字类型指针struct sockaddr*,传入的网络套接字类型struct sockaddr*需要强转成此类型指针 struct sockaddr*。
addrlen:(输入型参数)主机这个缓冲区大小。(socklen t就是unsigned int)
返回值:返回读到的字节数,错误就返回-1错误码被设置
(首次调用sendto函数的时候,我们的client会自动bind自己的ip和port)
(5)日志写法(可变参数)
在C/C++中会遇到需要定义使用可变参数的函数,例如printf就是,他的格式就是int printf(const char *format,...),对于这样类型的函数,他的实现实际上就是从format格式的指针指向的空间中读取可变参数的类型,然后根据可变参数的首地址读取相应的可变参数值
va_list ap; va_start 就是char* 指针类型。
void va_start(va_list ap, last); va_start(ap, format);——获取可变参数的首地址并赋值给ap
type va_arg(va_list ap, type); ——提取ap,根据type参数类型获取实参值返回
void va_end(va_list ap); ——将 ap 置空,即将可变参数指针归NULL
void va_copy(va_list dest, va_list src);
int vsnprintf(char *str, size_t size, const char *format, va_list ap); 通过读取format得到可变参数的类型,将用户格式化的可变参数内容写入数组str中
str:把格式化内容写进str这个数组中。size:被写入空间的大小 sizeof(str)-1(不包含'\0')。format:存储 可变参数的类型 的空间。ap:可变部分
Log.hpp
#pragma once#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...) level日志等级
{assert(level >= DEBUG);assert(level <= FATAL);char *name = getenv("USER");char logInfo[1024];va_list ap; // ap -> char*va_start(ap, format);//用离可变参数format最近的参数初始化apvsnprintf(logInfo, sizeof(logInfo)-1, format, ap);va_end(ap); // ap = NULLFILE *out = (level == FATAL) ? stderr:stdout;fprintf(out, "%s | %u | %s | %s\n", \log_level[level], \(unsigned int)time(nullptr),\name == nullptr ? "unknow":name,\logInfo);// char *s = format;// while(s){// case '%':// if(*(s+1) == 'd') int x = va_arg(ap, int);// break;// }
}
4.部分细节解释+代码(udp套接字)
易错:1. port_ 端口号是一个 2字节16位的整数,主机转网络要用htos,不能用htol(这个错误找了一天呐~)server.sin_port=htons(server_port);
htol 是转换四字节的,如果你传入一个两字节的数据,它就会自动进行补位,补位前面部分都是零,那这时候经过htol置换之后,前16位就变成零了,相当于你的程序跑去绑定零端口去了,就会绑定失败。
(1)INADDR_ANY
#define INADDR_ANY ((in_addr_t) 0x00000000)
local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());
①INADDR_ANY(这个宏的值就是0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法(解释:一般服务器只有一个IP,会自动bind这个IP;如果服务器有多个IP,会自动bind这个服务器的所有的IP——因为如果有两个IP:IP1和IP2,只bind一个IP1,那么只有传给IP1的报文会交给程序,IP2就不会提交报文)
云服务器有一些特殊情况:禁止你bind云服务器上的任何确定IP, 所以这里只能使用INADDR_ANY,如果你是虚拟机就可以bind自己虚拟机的IP,用ifconfig查看IP。
注意:这里inet_addr(ip_.c_str()) 当ip_是"0"时 等价于INADDR_ANY,INADDR_ANY 这个宏的值就是0,0是字符串风格还是网络风格无所谓,并且inet_addr 还会自动给我们进行 h—>n 主机字节序转网络字节序,即 inet_addr(0)=inet_addr(INADDR_ANY)=htonl(INADDR_ANY) 作用是一样的
(2)inet_addr(上面有)
in_addr_t inet_addr(const char *cp); 把字符串风格的IP地址 cp 转为4字节地址并返回。inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用。因为IP地址也是会发给对方的,所以除了做转化,inet_addr 还会自动给我们进行 h—>n 主机字节序转网络字节序(使用后就不用再调用htonl了)(INADDR_ANY 是0,所以h—>n转不转都行)
(3)bzero
bzero(&local,sizeof(1ocal)); ——bzero函数将从s开始的区域的前n个字节设置为0(字节包含'\0'). 也可以用memset代替
(4)本地通信:127.0.0.11——本地环回—代表本主机
客户端发送消息到本地的网络协议栈,但是不发送到网络,仅通过本地网络协议栈向上交付给另一个进程的缓冲区中。
(5)sock进loop会变成-1的问题
init中创建套接字不能加int,否则sock就是局部变量了
服务器创建dup的流程:
服务器:创建套接字,填充信息,bind绑定,recvfrom等待接收消息,checkOnlineUser 添加在线用户,messageRoute 消息路由
客户端:创建套接字,填充服务器的信息,创建线程去recvfrom等待路由消息,主线程发消息给服务器
Makefile
.PHONY:all
all:udpClient udpServerudpClient: udpClient.ccg++ -o $@ $^ -std=c++11 -lpthread
udpServer:udpServer.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f udpClient udpServer
udpClient.cc
#include <iostream>
#include <string>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>struct sockaddr_in server;static void Usage(std::string name)
{std::cout << "Usage:\n\t" << name << " server_ip server_port" << std::endl;
}void *recverAndPrint(void *args)
{while (true){int sockfd = *(int *)args;char buffer[1024];struct sockaddr_in temp;socklen_t len = sizeof(temp);//这个temp套接字结构体在这里不接收任何信息,只占位参数ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << "server echo# " << buffer << std::endl;}}
}// ./udpClient server_ip server_port
// 如果一个客户端要连接server必须知道server对应的ip和port
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}// 1. 根据命令行,设置要访问的服务器IPstd::string server_ip = argv[1];uint16_t server_port = atoi(argv[2]);// 2. 创建客户端// 2.1 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);assert(sockfd > 0);// 2.2 client 需不需要bind??? 需要bind,但是不需要用户自己bind,而是os自动给你bind
// 所谓的"不需要",指的是: 不需要用户自己bind端口信息!因为OS会自动给你绑定,你也最好这么做!
(OS随机申请生成一个进程并让这个进程去绑定运行客户端)
// 如果我非要自己bind呢?可以!严重不推荐!
// 所有的客户端软件 <-> 服务器 通信的时候,必须得有client[ip:port]<->server[ip:port]
// 为什么不需要用户自己bind端口信息呢??client很多,不能给客户端bind指定的port,port
可能被别的client使用了,你的client就无法启动了
// 那么server凭什么要bind呢??server提供的服务,必须被所有人知道!server不能随便改变!
server的端口号必须确定,但是客户端的端口号是多少不重要,因为没人连你的客户端,是你
连别人的服务器// 2.2 填写服务器对应的信息bzero(&server, sizeof server);server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());pthread_t t;pthread_create(&t, nullptr, recverAndPrint, (void *)&sockfd);// 3. 通讯过程std::string buffer;while (true){std::cerr << "Please Enter# ";std::getline(std::cin, buffer);// 发送消息给serversendto(sockfd, buffer.c_str(), buffer.size(), 0,(const struct sockaddr *)&server, sizeof(server)); // 首次调用sendto函数的时候,我们的client会自动bind自己的ip和port}close(sockfd);return 0;
}
udpServer.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Log.hpp"static void Usage(const std::string porc)
{std::cout << "Usage:\n\t" << porc << " port [ip]" << std::endl;
}/// @brief 我们想写一个简单的udpSever
/// 云服务器有一些特殊情况:
/// 1. 禁止你bind云服务器上的任何确定IP, 只能使用INADDR_ANY,如果你是虚拟机,随意
class UdpServer
{
public:UdpServer(int port, std::string ip = "") : port_((uint16_t)port), ip_(ip), sockfd_(-1){}~UdpServer(){}public:void init(){// 1. 创建socket套接字sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 就是打开了一个文件if (sockfd_ < 0){logMessage(FATAL, "socket:%s:%d", strerror(errno), sockfd_);exit(1);}logMessage(DEBUG, "socket create success: %d", sockfd_);// 2. 绑定网络信息,指明ip+port// 2.1 先填充基本信息到 struct sockaddr_instruct sockaddr_in local; // local在哪里开辟的空间? 用户栈 -> 临时变量 -> 写入内核中bzero(&local, sizeof(local)); // 可以用memset代替// 填充协议家族,域,选择是网络通信还是本地通信local.sin_family = AF_INET; sin_family就是开头的16位地址类型:AF_ INET// 填充服务器对应的端口号信息,一定是会发给对方的,port_一定会到网络中local.sin_port = htons(port_); port_类内成员是本地序列,要用htons转网络序列// 服务器都必须具有IP地址,42.192.83.143 "xx.yy.zz.aaa" ,字符串风格点分十进制 -> 4字节IP
-> uint32_t ip(每个数字是0~255,8bit)// INADDR_ANY(0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法// inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>nlocal.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());// 2.2 bind 网络信息if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1){logMessage(FATAL, "bind: %s:%d", strerror(errno), sockfd_);exit(2);}logMessage(DEBUG, "socket bind success: %d", sockfd_);// done}void start(){// 服务器设计的时候,服务器都是死循环char inbuffer[1024]; //将来读取到的数据,都放在这里char outbuffer[1024]; //将来发送的数据,都放在这里while (true){struct sockaddr_in peer; //输出型参数socklen_t len = sizeof(peer); //输入输出型参数// demo2// UDP无连接的// 对方给你发了消息,你想不想给对方回消息?要的!后面的两个参数是输出型参数ssize_t s = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0,(struct sockaddr *)&peer, &len);if (s > 0){//'\0'的值就是0,'0'的值是48,这里是存ASCII为0的'\0'inbuffer[s] = 0; //当做字符串}else if (s == -1){logMessage(WARINING, "recvfrom: %s:%d", strerror(errno), sockfd_);continue;}// 读取成功的,除了读取到对方的数据,你还要读取到对方的网络地址[ip:port]std::string peerIp = inet_ntoa(peer.sin_addr); //拿到了对方的IP,因为inet_ntoa这个函数参数类型
就是in_addr而不是in_addr_t,所以参数填peer.sin_addr而不是peer.sin_addr.s_addruint32_t peerPort = ntohs(peer.sin_port); // 拿到了对方的portcheckOnlineUser(peerIp, peerPort, peer); //如果存在,什么都不做,如果不存在,就添加// 打印出来客户端给服务器发送过来的消息logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer);// for(int i = 0; i < strlen(inbuffer); i++)// {// if(isalpha(inbuffer[i]) && islower(inbuffer[i])) outbuffer[i] = toupper(inbuffer[i]);// else outbuffer[i] = toupper(inbuffer[i]);// }messageRoute(peerIp, peerPort,inbuffer); //消息路由// 线程池!// sendto(sockfd_, outbuffer, strlen(outbuffer), 0, (struct sockaddr*)&peer, len);// demo1// logMessage(NOTICE, "server 提供 service 中....");// sleep(1);}}void checkOnlineUser(std::string &ip, uint32_t port, struct sockaddr_in &peer){std::string key = ip;key += ":";key += std::to_string(port);auto iter = users.find(key);if(iter == users.end()){users.insert({key, peer});}else{// iter->first, iter->second->// do nothing}}void messageRoute(std::string ip, uint32_t port, std::string info){std::string message = "[";message += ip;message += ":";message += std::to_string(port);message += "]# ";message += info;for(auto &user : users){
sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)&(user.second), sizeof(user.second));}}private:// 服务器必须得有端口号信息uint16_t port_;// 服务器必须得有ip地址std::string ip_;// 服务器的socket fd信息int sockfd_;// onlineuserstd::unordered_map<std::string, struct sockaddr_in> users;
};// struct client{
// struct sockaddr_in peer;
// uint64_t when; //peer如果在when之前没有再给我发消息,我就删除这用户
// }// ./udpServer port [ip]
int main(int argc, char *argv[])
{if (argc != 2 && argc != 3) //反面:argc == 2 || argc == 3{Usage(argv[0]);exit(3);}uint16_t port = atoi(argv[1]);std::string ip;if (argc == 3){ip = argv[2];}UdpServer svr(port, ip);svr.init();svr.start();return 0;
}// struct ip
// {
// uint32_t part1:8;
// uint32_t part2:8;
// uint32_t part3:8;
// uint32_t part4:8;
// }
// struct ip ip_;
// ip_.part1 = s.substr();
5.linux上的联网通信 步骤(udp套接字)
①makefile改成静态编译
②sz udpClient 把客户端发送到桌面
相当于发布软件
③ rz -e 用户下载软件
④chmod +x udpClient 将程序转为可执行程序
⑤在linux上通信可以开始了
打开服务器 ./udpServer,此时服务器阻塞等待有人发消息。各个客户端:./udpClient +服务器的公网IP+8080(端口号),就可以发消息通信了
6.windows做客户端,linux做服务器的联网通信 步骤(udp套接字)
①makefile改成静态编译
windows上的客户端代码框架
#pragma comment(lib, "ws2_32.lib") // 需要包含的链接库
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h> // windows socket 2.2版本int main()
{WSADATA wsaData; // 用作初始化套接字 WSAStartup(MAKEWORD(2, 2), &wsaData); // 初始化启动信息(初始套接字)客户端的创建套接字,填充服务器的信息,sendto通信closesocket(SendingSocket); // 释放套接字WSACleanup(); // 清空启动信息 system("pause");return 0;
}
全代码
发送这里的可执行程序就可以通信了
linux篇【12】:网络套接字<前序>—网络基础+udp套接字相关推荐
- Linux篇 | 多网卡绑定技术 binding 和 网络组nmcli
多网卡绑定 "Bonding" 和 "nmcli的网络组Network Teaming" 二者实现的功能一样,但从某种角度,网络组要比Bonding的技术要好 ...
- Day09: socket网络编程-OSI七层协议,tcp/udp套接字,tcp粘包问题,socketserver
今日内容:socket网络编程 1.OSI七层协议 2.基于tcp协议的套接字通信 3.模拟ssh远程执行命令 4.tcp的粘包问题及解决方案 5.基于udp协 ...
- 【Linux网络编程】UDP 套接字编程
[Linux网络编程]UDP 套接字编程 [1]用户数据报协议(UDP) UDP是一个简单的传输层协议,不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数 ...
- C++网络编程(二):UDP套接字编程
目录 基本特点 流程 基于UDP的数据I/O函数 UDP客户端套接字的地址分配 UDP套接字的数据边界 未连接UDP套接字.已连接UDP套接字 创建已连接UDP套接字 代码示例 参考资料 基本特点 U ...
- 【JavaEE】网络编程之TCP套接字、UDP套接字
目录 1.网络编程的基本概念 1.1为什么需要网络编程 1.2服务端与用户端 1.3网络编程五元组 1.4套接字的概念 2.UDP套接字编程 2.1UDP套接字的特点 2.2UDP套接字API 2.2 ...
- 《网络编程》基本 UDP 套接字编程
在前面文章中介绍了<UDP 协议>和<套接字数据传输>.UDP 协议和 TCP 协议不同,它是一种面向无连接.不可靠的传输层协议.在基于 UDP 套接字编程中,数据传输可用函数 ...
- 基本UDP套接字编程
使用UDP编写的一些常见的应用程序有:DNS(域名系统),NFS(网络文件系统)和SNMP(简单网络管理协议) UDP客户/服务器交互中发生的典型情形的时间线图. recvfrom和sendto函数 ...
- TCP/IP网络编程_第6章基于UDP的服务器端/客户端
6.1 理解 DUP 我们在第4章学习TCP的过程中, 还同时了解了 TCP/IP 协议. 在4层TCP/IP模型中, 上数第二层传输(Transport)层分为TCP和UDP这两种. 数据交换过程可 ...
- 计算机应用论文2500字,计算机应用论文2500字:计算机基础.doc
计算机应用论文2500字:计算机基础 计算机应用论文2500字:计算机基础 能力与知识的关系,相信大家都很清楚.知识不是能力,但却是获得能力的前提与基础.而要将知识转化为能力,需要个体的社会实践.下面 ...
最新文章
- ora-03115:不支持的网络数据类型 oracle,Oracle10g新增DBMS_FILE_TRANSFER包(二)
- MySQL入门之索引
- 信息学奥赛一本通 2029:【例4.15】水仙花数
- Nginx 附录A 编码风格 和 附录B 常用API
- 【LeetCode】【HOT】347. 前 K 个高频元素(哈希表+优先队列)
- 学校计算机协会招新策划案,本部 | 计算机协会招新中
- 和dump文件什么区别_将java进程转移到“解剖台”之前,法医都干了什么?
- socket 套接字
- plugin.super mysql_使用MySQ Clone Plugin部署MySQL Group Replication
- 阿里云宝塔Linux服务器管理面版初始化地址不能登入(原创)
- c语言for循环计算100以内奇数的和
- MySQL5.7.xx安装卡在Staring the server解决方案--亲测有效
- 指纹识别研究(一) 指纹的三级特征
- Android studio 图片按钮
- 【推荐】无需路由器,利用WIN7开启wifi,手机高速上网
- 织梦php模板在哪个文件夹,织梦模板如何修改默认templets模板文件夹名称的方法...
- 只需七天|金蝶云苍穹初级开发者训练营来了!
- 最新系统漏洞--Google TensorFlow拒绝服务漏洞
- taobao.top.oaid.client.decrypt( 端侧OAID解密 )
- source program