udp通信 c 语言,Windows程序设计 | 基于UDP的C/S通讯实现

作者小序--

今天翘课一天,本打算一个早上写完网络编程实验报告就出去溜达,结果从早上九点到这会没出过宿舍门,宅到不能再宅了,哎~,一首凉凉送给自己。。。。

分享一下干货吧~,windows下的网络编程和linux下的网络编程基本差不多,因为协议的实现机制完全相同,只是一些小细节上的区别,今天主要就说一下UDP数据报协议的实现原理:

接收端:1、创建数据报套接字;2、绑定本机地址和端口;3、等候接收数据;4、使用完成后关闭套接字。

发送端:1、创建数据报套接字;2、向指定地址和端口发送数据;3、使用完成后关闭套接字。

就是这么简单,不需要像传输控制协议那样,通过connect函数进行创建链接并进行三次握手四次挥手,只需要创建自己的套接字就可以进行通讯,但上帝是公平的,UDP不需要进行连接,也导致他的可靠性降低,很简单一个例子,如果客户需要给服务器发送消息,结果服务器这时候没有打开,而客户会以为消息发送成功,就会导致数据丢失。所以说没有什么模型是完美的,只能说取其精华,弃其糟粕罢了!

1、认真理解数据报套接字编程模型,在仔细阅读并调试运行UDPserve.cpp程序和UTPClient.cpp程序源代码,分析在服务端和客户端分别使用了如下的Winsock

API函数;

(1)socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。

-----------------------------------------------------------------#include

int

socket(int family,int type,int protocol);返回:非负描述字---成功-1---失败-----------------------------------------------------------------

第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。

(2)bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

-------------------------------------------------------------------#includeint

bind(int sockfd, const struct sockaddr * server, socklen_t

addrlen);返回:0---成功-1---失败-------------------------------------------------------------------

第一个参数是socket函数返回的套接口描述字;第二和第第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。

(3)recvfrom函数:UDP使用recvfrom()函数接收数据,他类似于标准的read(),但是在recvfrom()函数中要指明目的地址。

-------------------------------------------------------------------#include#includessize_t

recvfrom(int sockfd, void *buf, size_t len, int flags, struct

sockaddr * from, size_t *addrlen);返回接收到数据的长度---成功-1---失败-------------------------------------------------------------------

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。最后两个参数类似于accept的最后两个参数。

(4)sendto函数:UDP使用sendto()函数发送数据,他类似于标准的write(),但是在sendto()函数中要指明目的地址。

-------------------------------------------------------------------#include#includesize_t

sendto(int sockfd, const void *buf, size_t len, int flags, const

struct sockaddr * to, int addrlen);返回发送数据的长度---成功-1---失败-------------------------------------------------------------------

前三个参数等同于函数read()的前三个参数,flags参数是传输控制标志。参数to指明数据将发往的协议地址,他的大小由addrlen参数来指定。

2、修改UDPServer和UDPClient程序,设计一个简单的UDP通信程序,并达到以下要求:

(1)双方能相互发送数据,并显示接收到的数据。

(2)当收到对方的数据为“bye”时,能退出程序。

首先运行服务器端程序server,再运行客户端程序client并发送消息,继续发送一条消息。

在服务器程序关闭的情况,UDP仍然可以发送消息,但是不能确保送达目的地。

运行结果:

a4c26d1e5885305701be709a3d33442f.png Sever和Client进行通讯

3、编程验证实验思考题中问题。

(1)如红色区域所示,sendto()函数肯定会先执行,因为recvfrom()函数没有接收到来自服务器端的消息,返回值会小于0,所以recvfrom()函数不会首先执行。

a4c26d1e5885305701be709a3d33442f.png sendto()优先运行

4、服务器同多个客户端通信。

运行结果如下:经过测试,客户端发给主机的消息保存在消息队列中,例如一号客户端发送“一号”,二号客户端发送“二号”,然后三号客户端发送“三号”,这样服务器会接收这样的消息:一号、二号、三号。

a4c26d1e5885305701be709a3d33442f.png服务器和多个客户端通讯的消息顺序

5 思考题

1、能否在接收数据之间不进行bind()调用?如果能,请说明可能的情况。

可以不进行调用,但是前提是先调用sendto()函数,这样系统会自行进行套接字绑定,可以不进行bind()调用。如果先调用recvfrom()函数的话就必须进行bind绑定。

2、能否使用connect()连接对方?为什么?

UDP中可以使用connect系统调用,UDP中connect操作与TCP中connect操作有着本质区别,TCP中调用connect会引起三次握手,client与server建立连结,UDP中调用connect内核仅仅把对端ip和port记录下来,UDP中可以多次调用connect,TCP只能调用一次connect。

3、能否在不调用sendto()函数之前调用recvfom()函数。

服务端可以在不调用sendto()函数之前调用recvfom()函数,因为服务端要在接受到客户端数据后才发送数据到客户端,但是客户端必须先调用sendto函数后才能调用recvfom函数,因为,当没有客户端发来数据时,服务端一直处于监听状态,客户要先调用sendto函数才能让服务端不处于阻塞模式,然后再调用recvfom接受服务端发来的数据。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

#include#include#include#pragmacomment(lib,"wsock32.lib")usingnamespacestd;intmain()

{intcount;

WSADATA wsaData;

WSAStartup(MAKEWORD(2,2), &wsaData);// 创建套节字SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if(s == INVALID_SOCKET)

{

printf("Failed socket() %d \n", ::WSAGetLastError());return0;

}// 也可以在这里调用bind函数绑定一个本地地址,否则系统将会自动安排// 填写远程地址信息 sockaddr_in addr;sockaddr_in addr;

addr.sin_family = AF_INET;

addr.sin_port = htons(4567);intnLen =sizeof(addr);// 注意,这里要填写服务器程序所在机器的IP地址,如果你的计算机没有联网,直接使用127.0.0.1即可addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// 发送数据charreceivebuff[1024];charsendbuffer[1024];

printf("-----------------我是客户端-------------------------:\n");while(TRUE)

{intnRecv = ::recvfrom(s, receivebuff,1024,0, (sockaddr *)&addr, &nLen);if(nRecv >0)

{//接收数据system("color 0E");

receivebuff[nRecv] ='\0';

printf("从服务器接收到信息 >: %s\n", receivebuff);if(strcmp(receivebuff,"bye") ==0)

{

::closesocket(s);

WSACleanup();return0;

}

}//发送数据//scanf("%s", sendbuffer);gets(sendbuffer);

::sendto(s, sendbuffer, strlen(sendbuffer),0, (sockaddr *)&addr,sizeof(addr));

system("color 5A");

printf("客户端发送信息: %s\n", ++count, sendbuffer);if(strcmp(sendbuffer,"bye") ==0)

{

::closesocket(s);return0;

}

}return0;

}#include#include#include#include#pragmacomment(lib,"wsock32.lib")usingnamespacestd;intmain()

{intcount;

WSADATA wsaData;

WSAStartup(MAKEWORD(2,2), &wsaData);// 创建套节字SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if(s == INVALID_SOCKET)

{

printf("Failed socket() \n");return0;

}// 填充sockaddr_in结构sockaddr_in sin;

sin.sin_family = AF_INET;

sin.sin_port = htons(4567);

sin.sin_addr.S_un.S_addr = INADDR_ANY;// 绑定这个套节字到一个本地地址if(::bind(s, (LPSOCKADDR)&sin,sizeof(sin)) == SOCKET_ERROR)

{

printf("Failed bind() \n");return0;

}// 接收数据charreceivebuff[1024];charsendbuff[1024];

sockaddr_in addr;intnLen =sizeof(addr);

printf("-----------------我是服务器-------------------------:\n");while(TRUE)

{intnRecv = ::recvfrom(s, receivebuff,1024,0, (sockaddr *)&addr, &nLen);if(nRecv >0)

{

system("color 0E");

receivebuff[nRecv] ='\0';

printf("从客户端接收到信息 >: %s\n", receivebuff);if(strcmp(receivebuff,"bye") ==0)

{

::closesocket(s);return0;

}// scanf("%s", sendbuff);}

gets(sendbuff);

::sendto(s, sendbuff, strlen(sendbuff),0, (sockaddr *)&addr,sizeof(addr));

system("color 0F");

printf("服务器发送信息: %s\n", ++count, sendbuff);if(strcmp(sendbuff,"bye") ==0)

{

::closesocket(s);

WSACleanup();return0;

}

}return0;

}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值