UDP通信
本节代码详见https://gitee.com/hepburn0504-yyq/linux-class/tree/master/2023_04_14_UDPSocket
头文件
网络四件套(四个头文件)
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
AF_LOCAL AF_INET这些参数其实就是宏
函数
socket函数
头文件
#include <sys/socket.h>
#include <sys/types.h>
功能:创建一个通信终端,并且返回一个描述符
原型
int socket(int domain, int type, int protocol);
参数
domain:创建套接字的域,常用的域间套接字 AF_LOCAL(本地通信),网络套接字 AF_INET(网络通信 IPv4通信协议)
type:通信方式,TCP面向字节流 SOCK_STREAM, UDP用户数据报套接字 SOCK_DGRAM
ptotocol:一般设为0
返回值
创建失败返回-1,errno可以查看出错信息。创建成功返回一个文件描述符
bind函数
头文件
#include <sys/socket.h>
#include <sys/types.h>
功能:将用户设置的ip和port在内核中和我们的进程进行强关联
原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:套接字(即文件描述符),socket函数的返回值
addr:通信方式,TCP面向字节流 SOCK_STREAM, UDP用户数据报套接字 SOCK_DGRAM
addrlen:
返回值
绑定失败返回-1,errno可以查看出错信息。绑定成功返回0
bzero函数
头文件
#include <strings.h>
功能:将s按指定字节数置0
原型
void bzero(void *s, size_t n);
struct sockaddr_in结构体
头文件
#include <netinet/in.h>
#include <arpa/inet.h>
参数
sin_family:一般填的是bind函数的第一个参数
sin_addr:类型其实是uint32_t,4字节IP 【需要进行转换:点分10进制<-->4字节】
sin_port:类型其实是uint16_t,2字节端口号
-------------------源文件定义---------------
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)];
};
服务器的端口和IP也是要发送给客户机主机的,那么就要先将数据打包发给网络!IP和port都是超过1字节的,都得转换字节序。
1、port从主机字节序转换成网络字节序
头文件
#include <arpa/inet.h>
原型
uint32_t htonl(uint32_t hostlong);//4字节IP
uint16_t htons(uint16_t hostshort);//2字节port
uint32_t ntohl(uint32_t netlong);//4字节IP
uint16_t ntohs(uint16_t netshort);//2字节port
2、IP 点分十进制 --> 4字节 --> 网络字节序
头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数
in_addr_t inet_addr(const char *cp);//点分十进制 --> 4字节 --> 网络字节序
char *inet_ntoa(struct in_addr in);
recvfrom函数
头文件
#include <sys/types.h>
#include <sys/socket.h>
功能:接收对方发送的数据
原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数
sockfd:套接字(即文件描述符),传入socket函数的返回值
buf:自己设定的缓冲区
len:sizeof(buf),缓冲区的最大字节数
flags:0表示以阻塞方式读取
src_addr:输出型参数,用于存储对方的ip和port
addrlen:输入输出型参数,输入的是src_addr的大小,输出实际读到的字节数
返回值
绑定成功返回0,绑定失败返回-1,errno可以查看出错信息。
sendto函数
头文件
#include <sys/types.h>
#include <sys/socket.h>
功能:发送数据
原型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数
sockfd:套接字(即文件描述符),传入socket函数的返回值
buf:自己设定的缓冲区
len:sizeof(buf),缓冲区的最大字节数
flags:阻塞式发送
dest_addr:对方主机的套接字结构体信息
addrlen:dest_addr的大小
close函数
头文件
#include <unistd.h>
原型
int close(int fd);
参数
fd:套接字(即文件描述符),传入socket函数的返回值
popen函数
头文件
#include <stdio.h>
功能:1、执行command。 调用pipe,然后fork子进程(exec*),执行command命令;2、将执行结果交给FILE*指针,供用户读取
原型
FILE *popen(const char *command, const char *type);
strcasestr函数
头文件
#include <string.h>
原型
char *strstr(const char *haystack, const char *needle); //查找子串
char *strcasestr(const char *haystack, const char *needle); //忽略大小写查找子串
命令
netstat
功能:显示以指定方式连接当前主机的网络通信
参数 -n 把收到的消息尽量显示为数字 -u 显示udp形式的连接 -p 以进程形式显示 -a全部显示
127.0.0.1 是本地环回,即两个进程用此ip进行网络通信的时候,数据只会在本地协议栈中流动,不会把数据发送到网络中。所以通常用于本地网络服务器测试。
注意:云服务器无法绑定公网IP,也不建议绑定。
client和server两个在服务器上运行的程序,是linux环境的,多开几个窗口,可以通过服务器完成互通消息。
多线程版本client
两个线程,一个线程发消息,一个线程收消息。公共资源是套接字文件描述符。一个线程用它读取,一个线程用它写入。无并发的安全问题。
因为udp是全双工的,可以同时进行收发消息,而不受干扰。
与之相对应的概念是半双工,在一个时间只能读或者写。
收发消息独立窗口
此时发消息和收消息是显示在同一个窗口的,如何让收发消息分开?
创建有名管道!
输入端
mkfifo clientA
./udp_client 127.0.0.1 8080 > clientA // udp收到的数据输出到clientA里
显示端
cat < clientA
windows下的client
windows给服务器发消息,要用服务器的公网ip
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996)
#include <iostream>
#include <WinSock2.h>
#include <string>
#pragma comment(lib,"ws2_32.lib")
uint16_t port = 8080;
std::string serverIP = "43.143.229.43";
int main()
{
WSADATA WSAData;
WORD sockVersion = MAKEWORD(2, 2);
if (WSAStartup(sockVersion, &WSAData) != 0)
return 0;
SOCKET clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == clientSocket)
std::cout << "socket error!" << std::endl;
sockaddr_in dstAddr;
dstAddr.sin_family = AF_INET;
dstAddr.sin_port = htons(port);
dstAddr.sin_addr.S_un.S_addr = inet_addr(serverIP.c_str());
char buffer[1024];
while (true)
{
std::string message;
std::cout << "请输入:";
std::getline(std::cin, message);
sendto(clientSocket, message.c_str(), (int)message.size(), 0, (sockaddr*)&dstAddr, sizeof(dstAddr));
sockaddr_in tmp;
int len = sizeof(tmp);
size_t s = recvfrom(clientSocket, buffer, sizeof buffer, 0, (sockaddr*)&tmp, &len);
if (s > 0)
{
buffer[s] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
closesocket(clientSocket);
WSACleanup();
return 0;
}
目前遇到的问题:windows下的客户端无法向服务器发送消息,服务器没收到消息。
问题原因:未开放云服务器的8080号端口,防火墙未允许开放8080号端口。问题已解决。