linux和windows之间UDP通信的实现(linux环境下socket接口介绍)

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_t4字节IP 【需要进行转换:点分10进制<-->4字节】
	sin_port:类型其实是uint16_t2字节端口号
        
-------------------源文件定义---------------
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号端口。问题已解决。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux下,可以使用socket实现UDP通信UDP是一种无连接的协议,它不保证数据传输的可靠性,但是传输速度快,适用于实时性要求较高的场景。 使用socket实现UDP通信的步骤如下: 1. 创建socket:使用socket函数创建一个UDP套接字。 2. 绑定端口:使用bind函数将套接字绑定到一个本地端口上。 3. 发送数据:使用sendto函数向指定的目标地址发送数据。 4. 接收数据:使用recvfrom函数从指定的源地址接收数据。 需要注意的是,UDP是无连接的协议,因此在发送数据时需要指定目标地址和端口号,而在接收数据时需要指定源地址和端口号。 下面是一个简单的UDP通信示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 int main() { int sockfd; struct sockaddr_in addr; char buf[1024]; // 创建UDP套接字 sockfd = socket(AF_INET, SOCK_DGRAM, ); if (sockfd < ) { perror("socket"); exit(1); } // 绑定端口 memset(&addr, , sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < ) { perror("bind"); exit(1); } // 接收数据 while (1) { memset(buf, , sizeof(buf)); if (recvfrom(sockfd, buf, sizeof(buf), , NULL, NULL) < ) { perror("recvfrom"); exit(1); } printf("Received: %s\n", buf); } // 关闭套接字 close(sockfd); return ; } ``` 在上面的代码中,首先创建了一个UDP套接字,然后将其绑定到本地的8888端口上。接着进入一个循环,不断接收数据并打印出来。在接收数据时,由于UDP是无连接的协议,因此源地址和端口号都设置为NULL。 发送数据的代码如下: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 int main() { int sockfd; struct sockaddr_in addr; char buf[1024]; // 创建UDP套接字 sockfd = socket(AF_INET, SOCK_DGRAM, ); if (sockfd < ) { perror("socket"); exit(1); } // 设置目标地址 memset(&addr, , sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = inet_addr("127...1"); // 发送数据 strcpy(buf, "Hello, UDP!"); if (sendto(sockfd, buf, strlen(buf), , (struct sockaddr *)&addr, sizeof(addr)) < ) { perror("sendto"); exit(1); } // 关闭套接字 close(sockfd); return ; } ``` 在上面的代码中,首先创建了一个UDP套接字,然后设置目标地址为127...1:8888。接着将要发送的数据拷贝到buf中,使用sendto函数向目标地址发送数据。发送数据时需要指定目标地址和端口号。 以上就是在Linux下使用socket实现UDP通信的基本步骤和示例代码。 ### 回答2: UDP(User Datagram Protocol)是一种面向无连接的传输协议,它不像TCP协议一样需要建立连接、保证可靠传输。因此UDP具有简单、高效、灵活的特点,适合需要快速通信、可容忍少量数据丢失的场景。 在Linux下,使用C语言实现UDP通信需要使用socket编程。Socket用层与传输层之间接口,用于实现进程之间的通讯。 1. 创建socketLinux下创建socket需要使用socket函数,示例代码如下: ``` int socket(int domain, int type, int protocol); ``` 其中,domain参数指定通信域,可以是AF_INET(IPv4)或AF_INET6(IPv6);type参数指定socket类型,可以是SOCK_DGRAM(无连接的数据报文套接字,即UDP)或SOCK_STREAM(面向连接的流套接字,即TCP);protocol参数一般为0,在指定type时已经确定了具体的协议。函数返回创建的套接字描述符,失败时返回-1。 2. 绑定地址 在UDP通信中,不需要像TCP一样主动连接,可以通过bind将本机的指定端口绑定到socket上。示例代码如下: ``` int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ``` 其中,sockfd参数为创建的socket描述符,addr参数为指向本机IP地址和端口的sockaddr结构体指针,addrlen参数为sockaddr结构体大小。函数返回0表示成功,-1表示失败。 3. 发送数据 UDP协议支持一次发送一个数据报文,不需要像TCP一样分多次发送。示例代码如下: ``` 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参数为数据长度,dest_addr参数为目标地址的sockaddr结构体指针,addrlen参数为sockaddr结构体大小。flags参数可以选择为0或者MSG_DONTWAIT(非阻塞发送)。函数返回成功发送的字节数,失败时返回-1。 4. 接收数据 UDP协议支持一次接收一个数据报文,同样不需要像TCP一样分多次接收。示例代码如下: ``` ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ``` 其中,sockfd参数为socket描述符,buf参数为指向缓冲区的指针,len参数为缓冲区大小,src_addr参数为源地址的sockaddr结构体指针,addrlen参数为sockaddr结构体大小。flags参数可以选择为0或者MSG_DONTWAIT(非阻塞接收)。函数返回成功接收的字节数,失败时返回-1。 使用以上四个函数,可以实现简单的UDP通信,示例代码如下: ``` #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int sockfd; char buffer[BUFFER_SIZE]; struct sockaddr_in servaddr, cliaddr; // create socket sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket creation failed"); return -1; } // bind local address to socket memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(PORT); if (bind(sockfd, (const struct sockaddr *) &servaddr, sizeof(servaddr)) == -1) { perror("bind failed"); return -1; } // receive data from client and send response int len, n; len = sizeof(cliaddr); n = recvfrom(sockfd, (char *) buffer, BUFFER_SIZE, 0, (struct sockaddr *) &cliaddr, &len); buffer[n] = '\0'; printf("Client : %s\n", buffer); sendto(sockfd, (const char *) "Hello Client!", strlen("Hello Client!"), 0, (const struct sockaddr *) &cliaddr, len); // close socket close(sockfd); return 0; } ``` 以上代码为服务器端代码,它首先创建一个UDP socket,并绑定到本地IP地址和指定端口。之后通过recvfrom函数接收客户端发送的数据,并通过sendto函数发送响数据给客户端。客户端代码类似,只需要将recvfrom和sendto的顺序调换即可。 ### 回答3: 在Linux系统下,socket实现UDP通信非常常见,通常使用C语言编写。以下将介绍一下如何使用socket实现UDP通信。 首先,需要引入Linux socket库: ```c #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> ``` 然后,创建一个UDP socket: ```c int sockfd = socket(AF_INET, SOCK_DGRAM, 0); ``` 其中参数AF_INET表示IPv4地址,SOCK_DGRAM表示使用UDP协议,0表示默认选择协议。 接下来,需要指定目标地址和端口号: ```c struct sockaddr_in dest_addr; dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(port); inet_pton(AF_INET, ip, &dest_addr.sin_addr); ``` 其中port为目标端口号,ip为目标IP地址。 然后可以开始发送数据了,使用sendto函数: ```c int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ``` 其中,sockfd为已创建的socket文件描述符,buf为要发送的数据,len为数据长度,dest_addr为目标地址。发送成功时,函数将返回发送的字节数。 接收数据需要用recvfrom函数: ```c int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ``` 其中,sockfd为已创建的socket文件描述符,buf为接收到的数据缓冲区,len为缓冲区长度,src_addr为发送方地址,在成功接收到数据时,函数将返回接收的字节数。 在使用完socket后,需要使用close函数关闭socket: ```c int close(int sockfd); ``` 这样就可以在Linux系统下使用socket实现UDP通信了。当然,还有一些细节需要注意,例如,发送方必须先进行bind操作,接收方则需要进行listen操作。另外,由于UDP协议不提供可靠性保证,因此需要对数据进行校验和重传等处理,以确保数据传输的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值