创作

完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>

3天内不再提示

TCP和UDP的通信详细资料总结

来源:今日头条 作者:嵌入式大杂烩 2020-02-15 14:40 次阅读

一、什么是socket?

Socket的英文原义是“孔”或“插座”。在编程中,Socket被称做套接字,是网络通信中的一种约定。Socket编程的应用无处不在,我们平时用的QQ、微信、浏览器等程序,都与Socket编程有关。我们平时使用浏览器查资料,这个过程的技术原理是怎样的呢?

我们平时使用浏览器,大致就是这样的一个过程。这里有两个重要的名词:服务端与客户端。

Socket编程的目的就是如何实现这两端之间的通信。

1、Socket编程在嵌入式中也很重要

Socket编程不仅仅在互联网方面很重要,在我们的嵌入式方面也是非常的重要,因为现在很多电子设备都趋向于联网。比如很多嵌入式工作的招聘要求都会有这一条要求:

说一点题外话,还在学校的朋友,如果感觉到很迷茫,不知道学什么的时候,可以上招聘网站上看看自己未来工作相关的职位的任职要求,这样就可以总结自己的一些不足、比较有针对性的去学习。

二、Socket编程中的几个重要概念

Socket编程用于解决我们客户端与服务端之间通信的问题。我们平时多多少少都有听过IP地址、端口、TCP协议、UDP协议等概念,这些都与Socket编程中相关,想要知道怎么用起来,当然得先了解它们的一些介绍。下面看一下这些专业术语的一些要点介绍:

1、什么是IP地址?

IP地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址。IP地址被用来给Internet上的电脑一个编号。我们可以把“个人电脑”比作“一台电话”,那么“IP地址”就相当于“电话号码”。若计算机1知道计算机2的IP地址,则计算机1就能访问计算机2。

IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用点分十进制表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。

IP地址有IPv4与IPv6之分,现在用得较多的是IPv4。其中,有一个特殊的IP地址需要我们记住:127.0.0.1,这是回送地址,即本地机,一般用来测试使用。后边我们的实例中会用到。

关于IP地址还有很多知识要点,但是对于在Socket编程中的应用,我们暂且知道这么多就可以。

2、什么是TCP/IP端口?

上一点中我们提到,若计算机1知道计算机2的IP地址,则计算机1就能访问计算机2。但是,我们要访问计算机2中的不同的应用软件,则还得需要一个信息:端口。端口使用16bit进行编号,即其范围为:0~65536。但0~1023 的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80,FTP 服务的端口号为 21等。

3、什么是协议?

协议(Protocol)是通信双方进行数据交互的一种约定。如TCP、UDP协议:

(1)TCP协议

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,数据可以准确发送,数据丢失会重发。TCP协议常用于web应用中。

TCP连接(三次握手)

TCP传输起始时,客户端、服务端要完成三次数据交互工作才能建立连接,常称为三次握手。可形象比喻为如下对话:

客户端:服务端您好,我有数据要发给你,请求您开通访问权限。

服务端:客户端您好,已给您开通权限,您可以发送数据了。

客户端:收到,谢谢。

具体示意图为:

这里的SYN和ACK是都是标志位,其中SYN代表新建一个连接,ACK代表确认。其中m、n都是随机数。具体说明如:

第一次握手:SYN标志位被置位,客户端向服务端发送一个随机数m。

第二次握手:ACK、SYN标志位被置位。服务端向客户端发送m+1表示确认刚才收到的数据,同时向客户端发送一个随机数n。

第三次握手:ACK标志被置位。客户端向服务端发送n+1表示确认收到数据。

TCP断开(四次挥手)

TCP断开连接时,客户端、服务端要完成四次数据交互工作才能建立连接,常称为四次挥手。可形象比喻为如下对话:

客户端:服务端您好,我发送数据完毕了,即将和您断开连接。

服务端:客户端您好,我稍稍准备一下,再给您断开

服务端:客户端您好,我准备好了,您可以断开连接了。

客户端:好的,合作愉快!

具体示意图为:

这里的FIN也是一个标志位,代表断开连接。具体说明类似三次握手。

为什么建立连接只需要三次数据交互,而断开连接需要四次呢?

建立连接时,服务端在监听状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。

而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

(2)UDP协议

UDP(User Datagram Protocol, 用户数据报协议)是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,可以保证通讯效率,传输延时小。例如视频聊天应用中用的就是UDP协议,这样可以保证及时丢失少量数据,视频的显示也不受很大影响。

4、什么是协议族?

协议族是多个协议的统称。比如我们的TCP/IP协议族,其不仅仅是TCP协议、IP协议,而是多个协议的集合,其包含IP、TCP、UDP、FTP、SMTP等协议。

三、socket编程的API接口

1、Linux下的socket API接口

(1)创建socket:socket()函数

函数原型:

int socket(int af, int type, int protocol);

af参数:af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6,其前缀也可以是PF(Protocol Family),即PF_INET 和 PF_INET6。

type参数:type 为数据传输方式,常用的有 面向连接(SOCK_STREAM)方式(即TCP) 和 无连接(SOCK_DGRAM)的方式(即UDP)。

protocol参数:protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

使用示例:

创建TCP套接字:

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

创建UDP套接字:

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

(2)绑定套接字:bind()函数

函数原型:

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);

sock参数:sock 为 socket 文件描述符。

addr参数:addr 为 sockaddr 结构体变量的指针。

addrlen参数:addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。

使用示例:

将创建的套接字ServerSock与本地IP127.0.0.1、端口1314进行绑定:

/* 创建服务端socket */int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);/* 设置服务端信息 */struct sockaddr_in ServerSockAddr;memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零ServerSockAddr.sin_family = PF_INET; // 使用IPv4地址ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本机IP地址ServerSockAddr.sin_port = htons(1314); // 端口/* 绑定套接字 */bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

其中struct sockaddr_in类型的结构体变量用于保存IPv4的IP信息。若是IPv6,则有对应的结构体:

struct sockaddr_in6 { sa_family_t sin6_family; // 地址类型,取值为AF_INET6 in_port_t sin6_port; // 16位端口号 uint32_t sin6_flowinfo; // IPv6流信息 struct in6_addr sin6_addr; // 具体的IPv6地址 uint32_t sin6_scope_id; // 接口范围ID};

(3)建立连接:connect()函数

函数原型:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

参数与bind()的参数类似。

使用示例:

int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

(4)监听:listen()函数

函数原型:

int listen(int sock, int backlog);

sock参数:sock 为需要进入监听状态的套接字。

backlog参数:backlog 为请求队列的最大长度。

使用示例:

/* 进入监听状态 */listen(ServerSock, 10);

(5)接收请求:accept()函数

函数原型:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

sock参数:sock 为服务器端套接字。

addr参数:addr 为 sockaddr_in 结构体变量。

addrlen参数:addrlen 为参数 addr 的长度,可由 sizeof() 求得。

返回值:一个新的套接字,用于与客户端通信。

使用示例:

/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);

(6)关闭:close()函数

函数原型:

int close(int fd);

fd:要关闭的文件描述符。

使用示例:

close(ServerSock);

(7)数据的接收和发送

数据收发函数有几组:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

函数原型:

ssize_t read(int fd, void *buf, size_t count);ssize_t write(int fd, const void *buf, size_t count);ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

这里介绍一下recv()/send()、recvfrom()/sendto()。

recv()函数:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd参数:sockfd为要接收数据的套接字。

buf参数:buf 为要接收的数据的缓冲区地址。

len参数:len 为要接收的数据的字节数。

flags参数:flags 为接收数据时的选项,常设为0。

send()函数:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd参数:sockfd为要发送数据的套接字。

buf参数:buf 为要发送的数据的缓冲区地址。

len参数:len 为要发送的数据的字节数。

flags参数:flags 为发送数据时的选项,常设为0。

recvfrom()函数:

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);

sock:用于接收UDP数据的套接字;

buf:保存接收数据的缓冲区地址;

nbytes:可接收的最大字节数(不能超过buf缓冲区的大小);

flags:可选项参数,若没有可传递0;

from:存有发送端地址信息的sockaddr结构体变量的地址;

addrlen:保存参数 from 的结构体变量长度的变量地址值。

sendto()函数:

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);

sock:用于传输UDP数据的套接字;

buf:保存待传输数据的缓冲区地址;

nbytes:带传输数据的长度(以字节计);

flags:可选项参数,若没有可传递0;

to:存有目标地址信息的 sockaddr 结构体变量的地址;

addrlen:传递给参数 to 的地址值结构体变量的长度。

2、windows下的socket API接口

跟Linux下的差不多:

SOCKET socket(int af, int type, int protocol);int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);int listen(SOCKET sock, int backlog);SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); int closesocket( SOCKET s);int send(SOCKET sock, const char *buf, int len, int flags);int recv(SOCKET sock, char *buf, int len, int flags);int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);

3、TCP、UDP通信的socket编程过程图

(1)TCP通信socket编程过程

(2)UDP通信socket编程过程

四、socket的应用实例

1、基于TCP的本地客户端、服务端信息交互实例

本例的例子实现的功能为:本地TCP客户端往本地TCP服务端发送数据,TCP服务端收到数据则会打印输出,同时把原数据返回给TCP客户端。这个例子类似于我们在做单片机的串口实验时,串口上位机往我们的单片机发送数据,单片机收到数据则把该数据原样返回给上位机。

(1)windows的程序:

服务端程序tcp_server.c:

#include #include #define BUF_LEN 100int main(void){WSADATA wd;SOCKET ServerSock, ClientSock;char Buf[BUF_LEN] = {0};SOCKADDR ClientAddr;SOCKADDR_IN ServerSockAddr;int addr_size = 0, recv_len = 0;/* 初始化操作sock需要的DLL */WSAStartup(MAKEWORD(2,2),&wd); /* 创建服务端socket */if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))){printf("socket error!\n");exit(1);}/* 设置服务端信息 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零 ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址 ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本机IP地址 ServerSockAddr.sin_port = htons(1314); // 端口/* 绑定套接字 */ if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))){printf("bind error!\n");exit(1);}/* 进入监听状态 */if (-1 == listen(ServerSock, 10)){printf("listen error!\n");exit(1);}addr_size = sizeof(SOCKADDR);while (1){/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size))){printf("socket error!\n");exit(1);}/* 接受客户端的返回数据 */int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);printf("客户端发送过来的数据为:%s\n", Buf);/* 发送数据到客户端 */send(ClientSock, Buf, recv_len, 0);/* 关闭客户端套接字 */closesocket(ClientSock);/* 清空缓冲区 */memset(Buf, 0, BUF_LEN); }/*如果有退出循环的条件,这里还需要清除对socket库的使用*//* 关闭服务端套接字 *///closesocket(ServerSock); /* WSACleanup();*/return 0;}

客户端程序tcp_client.c:

#include #include #define BUF_LEN 100int main(void){WSADATA wd;SOCKET ClientSock;char Buf[BUF_LEN] = {0};SOCKADDR_IN ServerSockAddr;/* 初始化操作sock需要的DLL */WSAStartup(MAKEWORD(2,2),&wd); /* 向服务器发起请求 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); ServerSockAddr.sin_family = AF_INET; ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ServerSockAddr.sin_port = htons(1314);while (1){/* 创建客户端socket */if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))){printf("socket error!\n");exit(1);}if (-1 == connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))){printf("connect error!\n");exit(1);}printf("请输入一个字符串,发送给服务端:");gets(Buf);/* 发送数据到服务端 */send(ClientSock, Buf, strlen(Buf), 0);/* 接受服务端的返回数据 */recv(ClientSock, Buf, BUF_LEN, 0);printf("服务端发送过来的数据为:%s\n", Buf);memset(Buf, 0, BUF_LEN); // 重置缓冲区closesocket(ClientSock); // 关闭套接字}// WSACleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/return 0;}

我们上边的IP地址概念那一部分中,有强调127.0.0.1这个IP是一个特殊的IP地址,这是回送地址,即本地机,一般用来测试使用。这个例子中我们就用到了。此外,端口我们设置为1314,这是随意设置的,只要范围在1024~65536之间就可以。

本文使用的是gcc编译器编译,编译命令如下:

gcc tcp_client.c -o tcp_client.exe -lwsock32gcc tcp_server.c -o tcp_server.exe -lwsock32

这里必须要加-lwsock32这个参数用于链接windows下socket编程必须的winsock2这个库。若是使用集成开发环境,则需要把wsock32.lib放在工程目录下,并在我们代码中#include 下面加上一行 #pragma comment(lib, "ws2_32.lib")代码(这种情况本人未验证,有兴趣的朋友可尝试)。

实验现象:

先启动服务端程序tcp_server.exe,再启动客户端程序tcp_client.exe,并在客户端中输入字符串,则当服务端会接收到字符串时会打印输出,与此同时也会往客户端返回相同的数据:

(2)Linux的程序:

在linux下,“一切都是文件”,所以这里我们的套接字也当做文件来看待。

服务端程序linux_tcp_server.c:

#include #include #include #include #include #include #include #define BUF_LEN 100int main(void){int ServerFd, ClientFd;char Buf[BUF_LEN] = {0};struct sockaddr ClientAddr;int addr_len = 0, recv_len = 0;struct sockaddr_in ServerSockAddr; int optval = 1; /* 创建服务端文件描述符 */if (-1 == (ServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))){printf("socket error!\n");exit(1);}/* 设置服务端信息 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零 ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址 ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);// 自动获取IP地址 ServerSockAddr.sin_port = htons(6666); // 端口// 设置地址和端口号可以重复使用 if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0){printf("setsockopt error!\n");exit(1);}/* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */ if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(struct sockaddr))){printf("bind error!\n");exit(1);}/* 进入监听状态 */if (-1 == (listen(ServerFd, 10))){printf("listen error!\n");exit(1);}addr_len = sizeof(struct sockaddr);while (1){/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */if (-1 == (ClientFd = accept(ServerFd, (struct sockaddr*)&ClientAddr, &addr_len))){printf("accept error!\n");exit(1);}/* 接受客户端的返回数据 */if ((recv_len = recv(ClientFd, Buf, BUF_LEN, 0)) < 0){printf("recv error!\n");exit(1);}printf("客户端发送过来的数据为:%s\n", Buf);/* 发送数据到客户端 */send(ClientFd, Buf, recv_len, 0);/* 关闭客户端套接字 */close(ClientFd);/* 清空缓冲区 */memset(Buf, 0, BUF_LEN); }return 0;}

客户端程序linux_tcp_client.c:

#include #include #include #include #include #include #define BUF_LEN 100int main(void){int ClientFd;char Buf[BUF_LEN] = {0};struct sockaddr_in ServerSockAddr;/* 向服务器发起请求 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); ServerSockAddr.sin_family = AF_INET; ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ServerSockAddr.sin_port = htons(6666);while (1){/* 创建客户端socket */if (-1 == (ClientFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))){printf("socket error!\n");exit(1);}/* 连接 */if (-1 == connect(ClientFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr))){printf("connect error!\n");exit(1);}printf("请输入一个字符串,发送给服务端:");gets(Buf);/* 发送数据到服务端 */send(ClientFd, Buf, strlen(Buf), 0);memset(Buf, 0, BUF_LEN); // 重置缓冲区/* 接受服务端的返回数据 */recv(ClientFd, Buf, BUF_LEN, 0);printf("服务端发送过来的数据为:%s\n", Buf);memset(Buf, 0, BUF_LEN); // 重置缓冲区close(ClientFd); // 关闭套接字}return 0;}

Linux下编译就不需要添加-lwsock32参数:

gcc linux_tcp_server.c -o linux_tcp_servergcc linux_tcp_client.c -o linux_tcp_client

实验现象:

在调试这份程序时,出现了绑定错误:

经上网查询发现是端口重复使用,可以在调用bind()函数之前调用setsockopt()函数以解决端口重复使用的问题:

2、基于UDP的本地客户端、服务端信息交互实例

(1)windows的程序

服务端程序udp_server.c:

#include #include #define BUF_LEN 100int main(void){WSADATA wd;SOCKET ServerSock;char Buf[BUF_LEN] = {0};SOCKADDR ClientAddr;SOCKADDR_IN ServerSockAddr;int addr_size = 0;/* 初始化操作sock需要的DLL */WSAStartup(MAKEWORD(2,2),&wd); /* 创建服务端socket */if(-1 == (ServerSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))){printf("socket error!\n");exit(1);}/* 设置服务端信息 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零 ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址 ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取IP地址 ServerSockAddr.sin_port = htons(1314); // 端口/* 绑定套接字 */ if (-1 == (bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))){printf("bind error!\n");exit(1);}addr_size = sizeof(SOCKADDR);while (1){/* 接受客户端的返回数据 */int str_len = recvfrom(ServerSock, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);printf("客户端发送过来的数据为:%s\n", Buf);/* 发送数据到客户端 */sendto(ServerSock, Buf, str_len, 0, &ClientAddr, addr_size);/* 清空缓冲区 */memset(Buf, 0, BUF_LEN); }/*如果有退出循环的条件,这里还需要清除对socket库的使用*//* 关闭服务端套接字 *///closesocket(ServerSock); /* WSACleanup();*/return 0;}

客户端程序udp_client.c:

#include #include #define BUF_LEN 100int main(void){WSADATA wd;SOCKET ClientSock;char Buf[BUF_LEN] = {0};SOCKADDR ServerAddr;SOCKADDR_IN ServerSockAddr;int ServerAddrLen = 0;/* 初始化操作sock需要的DLL */WSAStartup(MAKEWORD(2,2),&wd); /* 创建客户端socket */if (-1 == (ClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))){printf("socket error!\n");exit(1);}/* 向服务器发起请求 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); ServerSockAddr.sin_family = PF_INET; ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ServerSockAddr.sin_port = htons(1314);ServerAddrLen = sizeof(ServerAddr);while (1){printf("请输入一个字符串,发送给服务端:");gets(Buf);/* 发送数据到服务端 */sendto(ClientSock, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));/* 接受服务端的返回数据 */recvfrom(ClientSock, Buf, BUF_LEN, 0, &ServerAddr, &ServerAddrLen);printf("服务端发送过来的数据为:%s\n", Buf);memset(Buf, 0, BUF_LEN); // 重置缓冲区}closesocket(ClientSock); // 关闭套接字// WSACleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/return 0;}

(2)Linux下的程序

服务端程序linux_udp_server.c:

#include #include #include #include #include #include #include #define BUF_LEN 100int main(void){int ServerFd;char Buf[BUF_LEN] = {0};struct sockaddr ClientAddr;struct sockaddr_in ServerSockAddr;int addr_size = 0; int optval = 1; /* 创建服务端socket */if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))){printf("socket error!\n");exit(1);}/* 设置服务端信息 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零 ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址 ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取IP地址 ServerSockAddr.sin_port = htons(1314); // 端口// 设置地址和端口号可以重复使用 if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0){printf("setsockopt error!\n");exit(1);}/* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */ if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr))){printf("bind error!\n");exit(1);}addr_size = sizeof(ClientAddr);while (1){/* 接受客户端的返回数据 */int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);printf("客户端发送过来的数据为:%s\n", Buf);/* 发送数据到客户端 */sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);/* 清空缓冲区 */memset(Buf, 0, BUF_LEN); }close(ServerFd);return 0;}

客户端程序linux_udp_client.c:

#include #include #include #include #include #include #define BUF_LEN 100int main(void){int ClientFd;char Buf[BUF_LEN] = {0};struct sockaddr ServerAddr;int addr_size = 0;struct sockaddr_in ServerSockAddr;/* 创建客户端socket */if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))){printf("socket error!\n");exit(1);}/* 向服务器发起请求 */ memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); ServerSockAddr.sin_family = PF_INET; ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ServerSockAddr.sin_port = htons(1314);addr_size = sizeof(ServerAddr);while (1){printf("请输入一个字符串,发送给服务端:");gets(Buf);/* 发送数据到服务端 */sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));/* 接受服务端的返回数据 */recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);printf("服务端发送过来的数据为:%s\n", Buf);memset(Buf, 0, BUF_LEN); // 重置缓冲区}close(ClientFd); // 关闭套接字return 0;}

实验现象:

实验现象如实例1。

五、总结

本笔记简单介绍了一些与socket编程相关的一些知识点:IP地址,什么是端口,协议等。重点介绍了TCP、UDP通信的一些原理及其API接口的用法,并给出了windows和linux下的TCP、UDP通信实例。以上就是关于socket编程的一些总结,如有错误,欢迎指出!

收藏 人收藏

    评论

    相关推荐

    CH579M运行网络例程UDP_Server,不管有没插网线总是重启是为什么?

    CH579M 运行网络例程UDP_Server, 不管有没插网线总是不定时重启(应该是NET库导至的重启),读R8_RESET_STA...
    发表于 05-20 07:28 44次 阅读

    CH32V307用作tcp server可以连接成功,但是接收不到数据是为什么?

    CH32V307 使用你们自研协议栈,用作TCP client可以,用作tcp server,可以连接成功,但是接收不到数据? void WCHNET...
    发表于 05-19 06:24 28次 阅读

    请问技术大佬们,CH32V307 MQTT例程有吗?

    RT,还有TCP UDP例程开头都没有提示需要配置FLASH和RAM大小,还好UART SERVER例程提示了,TCP CL...
    发表于 05-19 06:12 36次 阅读

    【开发教程9】AI语音人脸识别(会议记录仪-人脸打卡机)-WIFI-UDP网络通信

    CC3200AI实验教程——疯壳·开发板系列WIFI-UDP网络通信   UDP是User Datagram Protocol的简称...
    发表于 05-18 16:45 2191次 阅读
    【开发教程9】AI语音人脸识别(会议记录仪-人脸打卡机)-WIFI-UDP网络通信

    使用基于RT -Thread的W5500来写TCP客户端的程序

    在applications文件下新建my_tcp.c文件 #include #include /* 使用BSD socket,需要包含socket.h头文件 */ #include #...
    发表于 05-17 17:08 3041次 阅读
    使用基于RT -Thread的W5500来写TCP客户端的程序

    运行CH565开发板的TCP的程序进入HardFault_Handler中无法跳出怎么解决?

         运行CH565开发板的TCP的程序,添加自行发送的数据后,运行一段时间,或者数据发送过程中,将网线...
    发表于 05-17 07:31 129次 阅读

    LabVIEW连续TCP读写数据的时候性能差

    LabVIEW连续TCP读写数据的时候性能差 使用TCP在两个应用程序之间传输数据,首先将数据转换为字符串,然后通过TC...
    发表于 05-14 21:08 6543次 阅读

    怎么才能让CH579的TCP Server运行在多任务系统环境下?

    我打算让CH579的TCP Server运行在多任务系统环境下,希望得到指导。     while(1)     {   &...
    发表于 05-13 07:56 36次 阅读

    libwchnet.a库文件有bug没源文件不能修改要如何处理?

    经测试发现,MCU同时开1个TCP服务端(端口号:1111)和1个TCP客户端(端口号:2222),服务IP(PC端)都是192.168.3.100 此时...
    发表于 05-13 07:37 59次 阅读

    求助大佬!w5500用sock怎么试组播功能都不行怎么办呀

    /* 创建一个socket,类型是SOCK_DGRAM,UDP类型 */     if ((sock_group = socket(AF_INET, SOC...
    发表于 05-11 09:37 869次 阅读

    SocketPlane多主机容器网络

    ./oschina_soft/socketplane.zip
    发表于 05-11 09:29 8次 阅读
    SocketPlane多主机容器网络

    libschannel TCP/IP通道双向保护

    ./oschina_soft/libschannel.zip
    发表于 05-09 09:46 10次 阅读
    libschannel TCP/IP通道双向保护

    KUKA机器人带FDS拧紧设备的TCP校准应用

    将带有固定板的螺丝刀插入底板的螺栓上并精确居中。然后在 X 方向上移动负 60mm 并接受这一点。以....
    的头像 机器人及PLC自动化应用 发表于 05-06 15:19 303次 阅读

    详解TCP报文的头部结构

    TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一....
    的头像 C语言编程学习基地 发表于 05-05 15:08 147次 阅读

    dsnat NAT工具

    dsnat.zip
    发表于 05-05 10:20 15次 阅读
    dsnat NAT工具

    tcpxm基于pylibcap的抓包工具

    tcpxm.zip
    发表于 05-05 10:19 12次 阅读
    tcpxm基于pylibcap的抓包工具

    rawip4j Java链路层封包协议

    rawip4j.zip
    发表于 05-05 09:32 11次 阅读
    rawip4j Java链路层封包协议

    Tcp-DNS-proxy TCP DNS代理

    Tcp-DNS-proxy.zip
    发表于 04-29 10:44 33次 阅读
    Tcp-DNS-proxy TCP DNS代理

    packetdrill网络栈测试脚本

    packetdrill.zip
    发表于 04-29 09:57 16次 阅读
    packetdrill网络栈测试脚本

    PacketSender网络包发送/接收工具

    PacketSender.zip
    发表于 04-29 09:36 20次 阅读
    PacketSender网络包发送/接收工具

    Mr.2本地的服务暴露到外网

    mr2.zip
    发表于 04-28 09:22 8次 阅读
    Mr.2本地的服务暴露到外网

    iperf3带宽性能测量工具

    iperf.zip
    发表于 04-27 14:31 34次 阅读
    iperf3带宽性能测量工具

    Sheeps服务器压力测试框架

    gitee-Sheeps.zip
    发表于 04-27 10:52 15次 阅读
    Sheeps服务器压力测试框架

    Log Kit By Go日志中心平台

    LogkitByGo.zip
    发表于 04-27 10:30 14次 阅读
    Log Kit By Go日志中心平台

    tcprstat TCP分析工具

    tcprstat.zip
    发表于 04-26 09:13 20次 阅读
    tcprstat TCP分析工具

    tcplstat网络监控工具

    gitee-tcplstat.zip
    发表于 04-25 09:17 30次 阅读
    tcplstat网络监控工具

    全硬件TCP/IP嵌入式以太网控制器w5500中文资料

    W5500 是一款全硬件 TCP/IP 嵌入式以太网控制器,为嵌入式系统提供了更加简易的互联网连接方....
    发表于 04-21 15:47 297次 阅读

    为什么NVMe/TCP是数据中心的更优选择

    与SCSI、ISCSI、SAS或SATA 接口不同,NVMe实现了针对多核服务器 CPU 优化的简化....
    的头像 十亿少男的梦 发表于 04-18 10:22 261次 阅读
    为什么NVMe/TCP是数据中心的更优选择

    嵌入式项目中,UDP与TCP该怎么选?

    在单片机应用程序开发中可能用得比较多有RS485,CAN通信等等相对简洁一点的总线,由于所选用的单片....
    的头像 嵌入式ARM 发表于 04-14 14:56 267次 阅读

    MODBUS标准及应用层协议和服务规范

      该标准包括两个通信规程中使用的 MODBUS 应用层协议和服务规范:   ·串行链路上的 ....
    发表于 04-13 14:57 60次 阅读

    基于openharmony适配移植的Socket通讯教程

    项目介绍 项目名称:OkSocket 所属系列:openharmony的第三方组件适配移植 功能:是....
    发表于 04-12 09:13 24次 阅读

    S7-1200与S7-300 PN口之间的以太网通信方法

    S7-1200 与 S7-300 PN 口之间的以太网通信可以通过 UDP 协议来实现,使用的通信指....
    的头像 机器人及PLC自动化应用 发表于 04-11 09:44 318次 阅读

    上位机发送FINS UDP命令读写PLC数据

     案例简介:   1.掌握使用SocketTool软件发送FINS/UDP命令读写PLC数据。....
    发表于 04-06 14:47 64次 阅读

    S7-300与第三方的TCP通信_Server(STEP7)

    SIMATIC S7- 300 CPU集成了 PROFINET 接口,该接口除了具备连接 PROFI....
    的头像 机器人及PLC自动化应用 发表于 04-03 09:13 903次 阅读

    UDP不属于面向连接的通信

    总之,使用UDP协议进行信息的传输之前不需要建立连接。换句话说就是客户端向服务器发送信息,客户端只需....
    的头像 机器人及PLC自动化应用 发表于 04-02 10:06 331次 阅读

    S7 200 SMART之间TCP协议通讯的实现

    TCP是一个因特网核心协议。在通过以太网通信的主机上运行的应用程序之间,TCP 提供了可靠、有序并能....
    的头像 机器人及PLC自动化应用 发表于 03-30 15:23 924次 阅读

    S7-200 SMART之间TCP通信例程

    TCP是一个因特网核心协议。在通过以太网通信的主机上运行的应用程序之间,TCP 提供了可靠、有序并能....
    的头像 机器人及PLC自动化应用 发表于 03-28 14:23 664次 阅读

    S7-200 SMART Modbus TCP客户端与服务器如何编程及通信过程

    STEP7-Micro/WIN SMART 从V2.4 版本开始,软件中直接集成 Modbus TC....
    的头像 机器人及PLC自动化应用 发表于 03-15 09:02 1222次 阅读

    TCP keepalive机制具体是怎么样的

    今天,聊一个有趣的问题:拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗?
    的头像 程序人生 发表于 03-11 16:50 737次 阅读

    T3M1手持宽带自组网电台测试方案

    T3M系列宽带自组网电台硬件上采用通用SDR平台(Zynq7035+AD9361),接收灵敏度可达-....
    发表于 03-02 10:08 63次 阅读
    T3M1手持宽带自组网电台测试方案

    基于STM32的WEB服务器设计

    STM32控制ENC28J60+UIP协议栈创建TCP服务器(WEB服务器),支持浏览器访问完成数据....
    的头像 DS小龙哥-嵌入式技术 发表于 02-28 14:03 1569次 阅读
    基于STM32的WEB服务器设计

    如何实现模块与模块之间点对点的通信

      ESP8266有三种模式可以选择,AP/STA/AP+STA,下面我们要实现的是模块与模块之间点....
    的头像 辰光 发表于 02-28 10:48 812次 阅读

    迪文串口屏,PC发送指令集调试出现的问题及解决方法

    这里使用的是迪文的串口屏,T5UIC1芯片的,因为第一次接触,就买了全套的配件因为购买了转接板和US....
    发表于 01-14 10:42 79次 阅读
    迪文串口屏,PC发送指令集调试出现的问题及解决方法

    MQTT客户端移植C语言

    mqtt客户端源码地址:https://github.com/eclipse/paho.mqtt.e....
    发表于 01-13 13:50 85次 阅读
    MQTT客户端移植C语言

    用Android Studio 创建Socket客户端向单片机发送数据——笔记

    用Android Studio 创建Socket客户端向单片机发送数据功能快捷键合理的创建标题,有助....
    发表于 01-04 18:59 172次 阅读
    用Android Studio 创建Socket客户端向单片机发送数据——笔记

    protues仿真器串口数据上传至Web服务器(COM口 与 TCP socket之间数据传输)

    protues仿真器串口数据上传至Web服务器(COM口 与 TCP socket之间数据传输)应用....
    发表于 12-29 18:56 106次 阅读
    protues仿真器串口数据上传至Web服务器(COM口 与 TCP socket之间数据传输)

    单片机GPRS模块与web端通信

    最近开搞毕设,拿起了单片机,因为最近实习一直在前端和gis,所以就想弄个gps和web端地图的交互软....
    发表于 12-29 18:52 97次 阅读
    单片机GPRS模块与web端通信

    stm32 usb虚拟串口收发数据

    stm32 usb虚拟串口收发数据1.stm32 usb虚拟串口发送数据函数CDC_Transmit....
    发表于 12-28 19:45 268次 阅读
    stm32 usb虚拟串口收发数据

    stm32f407+rtthread 使用 ec20 ppp拨号

    stm32f407+rtthread 使用 ec20 ppp拨号开发环境一、工程创建二、模块添加三、....
    发表于 12-27 18:41 175次 阅读
    stm32f407+rtthread 使用 ec20 ppp拨号

    【Arduino】NRF24L01无线模块6通道通信

    在我们使用NRF24L01 2.4G无线通讯模块时往往不只是发送一个数据,这里我以我的项目总使用的6....
    发表于 12-17 18:09 232次 阅读
    【Arduino】NRF24L01无线模块6通道通信

    基于esp8266的nodemcu模块从白痴到不太白痴

    esp8266 芯片是乐鑫(espressif)产的,安信可公司用这芯片做出了模组即封装好的小模块,....
    发表于 12-17 17:55 74次 阅读
    基于esp8266的nodemcu模块从白痴到不太白痴

    基于esp8266的nodemcu模块从白痴到不太白痴

    esp8266 芯片是乐鑫(espressif)产的,安信可公司用这芯片做出了模组即封装好的小模块,....
    发表于 12-17 17:52 95次 阅读
    基于esp8266的nodemcu模块从白痴到不太白痴

    建立UDP到虚拟串口的映射软件 : USR-VCOM,并应用到WiFi调试ESP32

     §01 虚拟串口一、应用背景1、WiFi转串口模块利用局域网网络转串口模块可以方便进行自....
    发表于 12-16 16:52 153次 阅读
    建立UDP到虚拟串口的映射软件 : USR-VCOM,并应用到WiFi调试ESP32

    []FET-430&SIM508 研究日志 11.3.31

    原文地址:FET-430&SIM508研究日志11.3.31 作者:半岛鱼 3月31....
    发表于 12-14 19:09 83次 阅读
    []FET-430&SIM508 研究日志 11.3.31

    TCP/IP需要掌握的知识点(面试高频)

    我司研发销售6轴、9轴电子罗盘(陀螺仪|加速计|磁力计)、倾角传感器、姿态传感器,惯导、数据采集盒、....
    的头像 jf_70090122 发表于 12-07 10:31 81次 阅读
    TCP/IP需要掌握的知识点(面试高频)

    教你如何通过MCU配置S2E为TCP Server的工作模式

    W5500S2E-S1是一款工业级串口转以太网模块,支持多种波特率,从1.2Kbps至1.152Mb....
    发表于 12-06 19:36 94次 阅读
    教你如何通过MCU配置S2E为TCP Server的工作模式

    RS485串口转以太网接入MODBUS TCP第三方云平台

    RS485串口转以太网接入MODBUS TCP第三方云平台
    发表于 12-06 14:57 511次 阅读

    美格智能Cat.1模组顺利通过多项国际认证

    Cat.1市场发展空前盛况,美格智能承接海外物联网需求,开启全球布局之路。截至今日,美格智能Cat.....
    的头像 美格智能 发表于 12-02 16:33 879次 阅读
    美格智能Cat.1模组顺利通过多项国际认证