C语言之TCP和UDP代码示意

tcp/ip协议详解:https://www.cnblogs.com/fengzanfeng/articles/1339347.html

操作系统原理:https://blog.csdn.net/Shallwen_Deng/article/details/100604985

TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输 ………… 什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP ……

有些应用场景对可靠性要求不高会用到UPD,比如长视频,要求速率

小结TCP与UDP的区别:

1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;

5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

TCP与UDP区别总结:

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

按照数据流的传输方向分为:单工、半双工、全双工:http://www.360doc.com/content/17/1030/20/42387867_699542887.shtml、

“TCP是一种流模式的协议,UDP是一种数据报模式的协议”,
1、TCP
TCP连接给另一端发送数据,你只调用了一次write,发送了100个字节,但是对方可以分10次收完,每次10个字节;你也可以调用10次write,每次10个字节,但是对方可以一次就收完。(假设数据都能到达)但是,你发送的数据量不能大于对方的接收缓存(流量控制),如果你硬是要发送过量数据,则对方的缓存满了就会把多出的数据丢弃。 这种情况是设置非阻塞I/O模型,会把内存耗尽,因为socket是存在内核中的。
2、UDP
UDP和TCP不同,发送端调用了几次write,接收端必须用相同次数的read读完。UPD是基于报文的,在接收的时候,每次最多只能读取一个报文,报文和报文是不会合并的,如果缓冲区小于报文长度,则多出的部分会被丢弃。也就说,如果不指定MSG_PEEK标志,每次读取操作将消耗一个报文。
3、为什么
其实,这种不同是由TCP和UDP的特性决定的。TCP是面向连接的,也就是说,在连接持续的过程中,socket中收到的数据都是由同一台主机发出的,因此,知道保证数据是有序的到达就行了,至于每次读取多少数据自己看着办。
而UDP是无连接的协议,也就是说,只要知道接收端的IP和端口,且网络是可达的,任何主机都可以向接收端发送数据。这时候,如果一次能读取超过一个报文的数据,则会乱套。比如,主机A向发送了报文P1,主机B发送了报文P2,如果能够读取超过一个报文的数据,那么就会将P1和P2的数据合并在了一起。

 

 

TCP链接代码示意:

Linux 下的代码,server.cpp 是服务器端代码,client.cpp 是客户端代码,要实现的功能是:客户端从服务器读取一个字符串并打印出来。


服务器端代码 server.cpp:

 
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8.  
  9. int main(){
  10. //创建套接字
  11. int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  12.  
  13. //将套接字和IP、端口绑定
  14. struct sockaddr_in serv_addr;
  15. memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
  16. serv_addr.sin_family = AF_INET; //使用IPv4地址
  17. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
  18. serv_addr.sin_port = htons(1234); //端口
  19. bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  20.  
  21. //进入监听状态,等待用户发起请求
  22. listen(serv_sock, 20);
  23.  
  24. //接收客户端请求
  25. struct sockaddr_in clnt_addr;
  26. socklen_t clnt_addr_size = sizeof(clnt_addr);
  27. int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
  28.  
  29. //向客户端发送数据
  30. char str[] = "Hello World!";
  31. write(clnt_sock, str, sizeof(str));
  32.  
  33. //关闭套接字
  34. close(clnt_sock);
  35. close(serv_sock);
  36.  
  37. return 0;
  38. }


客户端代码 client.cpp:

 
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7.  
  8. int main(){
  9. //创建套接字
  10. int sock = socket(AF_INET, SOCK_STREAM, 0);
  11.  
  12. //向服务器(特定的IP和端口)发起请求
  13. struct sockaddr_in serv_addr;
  14. memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
  15. serv_addr.sin_family = AF_INET; //使用IPv4地址
  16. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
  17. serv_addr.sin_port = htons(1234); //端口
  18. connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  19.  
  20. //读取服务器传回的数据
  21. char buffer[40];
  22. read(sock, buffer, sizeof(buffer)-1);
  23.  
  24. printf("Message form server: %s\n", buffer);
  25.  
  26. //关闭套接字
  27. close(sock);
  28.  
  29. return 0;
  30. }


先编译 server.cpp 并运行:

[admin@localhost ~]$ g++ server.cpp -o server
[admin@localhost ~]$ ./server
|

正常情况下,程序运行到 accept() 函数就会被阻塞,等待客户端发起请求。

接下来编译 client.cpp 并运行:

[admin@localhost ~]$ g++ client.cpp -o client
[admin@localhost ~]$ ./client
Message form server: Hello World!
[admin@localhost ~]$

client 运行后,通过 connect() 函数向 server 发起请求,处于监听状态的 server 被激活,执行 accept() 函数,接受客户端的请求,然后执行 write() 函数向 client 传回数据。client 接收到传回的数据后,connect() 就运行结束了,然后使用 read() 将数据读取出来。

需要注意的是:
1) server 只接受一次 client 请求,当 server 向 client 传回数据后,程序就运行结束了。如果想再次接收到服务器的数据,必须再次运行 server,所以这是一个非常简陋的 socket 程序,不能够一直接受客户端的请求。

2) 上面的源文件后缀为.cpp,是C++代码,所以要用g++命令来编译。

C++和C语言的一个重要区别是:在C语言中,变量必须在函数的开头定义;而在C++中,变量可以在函数的任何地方定义,使用更加灵活。这里之所以使用C++代码,是不希望在函数开头堆砌过多变量。

源码解析

1) 先说一下 server.cpp 中的代码。

第11行通过 socket() 函数创建了一个套接字,参数 AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用面向连接的数据传输方式,IPPROTO_TCP 表示使用 TCP 协议。在 Linux 中,socket 也是一种文件,有文件描述符,可以使用 write() / read() 函数进行 I/O 操作。

第19行通过 bind() 函数将套接字 serv_sock 与特定的IP地址和端口绑定,IP地址和端口都保存在 sockaddr_in 结构体中。

socket() 函数确定了套接字的各种属性,bind() 函数让套接字与特定的IP地址和端口对应起来,这样客户端才能连接到该套接字。

第22行让套接字处于被动监听状态。所谓被动监听,是指套接字一直处于“睡眠”中,直到客户端发起请求才会被“唤醒”。

第27行的 accept() 函数用来接收客户端的请求。程序一旦执行到 accept() 就会被阻塞(暂停运行),直到客户端发起请求。

第31行的 write() 函数用来向套接字文件中写入数据,也就是向客户端发送数据。

和普通文件一样,socket 在使用完毕后也要用 close() 关闭。

2) 再说一下 client.cpp 中的代码。client.cpp 中的代码和 server.cpp 中有一些区别。

第19行代码通过 connect() 向服务器发起请求,服务器的IP地址和端口号保存在 sockaddr_in 结构体中。直到服务器传回数据后,connect() 才运行结束。

 

 

第23行代码通过 read() 从套接字文件中读取数据。

 

 

 

 

 

=====================

UDP代码示意:windows下的代码

 

下面给出Windows下的代码,Linux与此类似,不再赘述。

服务器端 server.cpp:

 
  1. #include <stdio.h>
  2. #include <winsock2.h>
  3. #pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
  4.  
  5. #define BUF_SIZE 100
  6.  
  7. int main(){
  8. WSADATA wsaData;
  9. WSAStartup( MAKEWORD(2, 2), &wsaData);
  10.  
  11. //创建套接字
  12. SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
  13.  
  14. //绑定套接字
  15. sockaddr_in servAddr;
  16. memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充
  17. servAddr.sin_family = PF_INET; //使用IPv4地址
  18. servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取IP地址
  19. servAddr.sin_port = htons(1234); //端口
  20. bind(sock, (SOCKADDR*)&servAddr, sizeof(SOCKADDR));
  21.  
  22. //接收客户端请求
  23. SOCKADDR clntAddr; //客户端地址信息
  24. int nSize = sizeof(SOCKADDR);
  25. char buffer[BUF_SIZE]; //缓冲区
  26. while(1){
  27. int strLen = recvfrom(sock, buffer, BUF_SIZE, 0, &clntAddr, &nSize);
  28. sendto(sock, buffer, strLen, 0, &clntAddr, nSize);
  29. }
  30.  
  31. closesocket(sock);
  32. WSACleanup();
  33. return 0;
  34. }

代码说明:
1) 第12行代码在创建套接字时,向 socket() 第二个参数传递 SOCK_DGRAM,以指明使用UDP协议。

2) 第18行代码中使用htonl(INADDR_ANY)来自动获取IP地址。

利用常数 INADDR_ANY 自动获取IP地址有一个明显的好处,就是当软件安装到其他服务器或者服务器IP地址改变时,不用再更改源码重新编译,也不用在启动软件时手动输入。而且,如果一台计算机中已分配多个IP地址(例如路由器),那么只要端口号一致,就可以从不同的IP地址接收数据。所以,服务器中优先考虑使用INADDR_ANY;而客户端中除非带有一部分服务器功能,否则不会采用。

客户端 client.cpp:

 
  1. #include <stdio.h>
  2. #include <WinSock2.h>
  3. #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
  4.  
  5. #define BUF_SIZE 100
  6.  
  7. int main(){
  8. //初始化DLL
  9. WSADATA wsaData;
  10. WSAStartup(MAKEWORD(2, 2), &wsaData);
  11.  
  12. //创建套接字
  13. SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0);
  14.  
  15. //服务器地址信息
  16. sockaddr_in servAddr;
  17. memset(&servAddr, 0, sizeof(servAddr)); //每个字节都用0填充
  18. servAddr.sin_family = PF_INET;
  19. servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  20. servAddr.sin_port = htons(1234);
  21.  
  22. //不断获取用户输入并发送给服务器,然后接受服务器数据
  23. sockaddr fromAddr;
  24. int addrLen = sizeof(fromAddr);
  25. while(1){
  26. char buffer[BUF_SIZE] = {0};
  27. printf("Input a string: ");
  28. gets(buffer);
  29. sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr*)&servAddr, sizeof(servAddr));
  30. int strLen = recvfrom(sock, buffer, BUF_SIZE, 0, &fromAddr, &addrLen);
  31. buffer[strLen] = 0;
  32. printf("Message form server: %s\n", buffer);
  33. }
  34.  
  35. closesocket(sock);
  36. WSACleanup();
  37. return 0;
  38. }

先运行 server,再运行 client,client 输出结果为:

Input a string: C语言中文网
Message form server: C语言中文网
Input a string: c.biancheng.net Founded in 2012
Message form server: c.biancheng.net Founded in 2012
Input a string:


从代码中可以看出,server.cpp 中没有使用 listen() 函数,client.cpp 中也没有使用 connect() 函数,因为 UDP 不需要连接。

 

 

 

 

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值