Linux网络编程之TCP/IP通信基础以及例程分享

目录

协议的概念

典型协议举例

什么是TCP/IP协议栈

套接字编程基础

TCP/IP通信相关API函数

socket()

bind()

listen()

accept()

connect()

TCP/IP通信的C/S模型分析

C/S通信模型相关例程

例程分析

客户端代码:

服务器端代码:

例程分享

客户端

服务器端

协议的概念

        从应用的角度出发,协议可理解为“规则”,是数据传输和数据的解释的规则。

        假设,A、B双方欲传输文件。规定:

        第一次,传输文件名,接收方接收到文件名,应答OK给传输方;

        第二次,发送文件的尺寸,接收方接收到该数据再次应答一个OK;

        第三次,传输文件内容。同样,接收方接收数据完成后应答OK表示文件内容接收成功。

        由此,无论A、B之间传递何种文件,都是通过三次数据传输来完成。A、B之间形成了一个最简单的数据传输规则。双方都按此规则发送、接收数据。A、B之间达成的这个相互遵守的规则即为协议。

        这种仅在A、B之间被遵守的协议称之为原始协议。当此协议被更多的人采用,不断的增加、改进、维护、完善。最终形成一个稳定的、完整的文件传输协议,被广泛应用于各种文件传输过程中。该协议就成为一个标准协议。最早的ftp协议就是由此衍生而来。

典型协议举例

传输层 常见协议有TCP/UDP协议。
应用层 常见的协议有HTTP协议,FTP协议。
网络层 常见协议有IP协议、ICMP协议、IGMP协议。
网络接口层 常见协议有ARP协议、RARP协议。
TCP传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
UDP用户数据报协议(User Datagram Protocol)是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
HTTP超文本传输协议(Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络协议。
FTP文件传输协议(File Transfer Protocol)
IP协议是因特网互联协议(Internet Protocol)
ICMP协议是Internet控制报文协议(Internet Control Message Protocol)它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。
IGMP协议是 Internet 组管理协议(Internet Group Management Protocol),是因特网协议家族中的一个组播协议。该协议运行在主机和组播路由器之间。
ARP协议是正向地址解析协议(Address Resolution Protocol),通过已知的IP,寻找对应主机的MAC地址。
RARP是反向地址转换协议,通过MAC地址确定IP地址。

什么是TCP/IP协议栈

        TCP/IP网络协议栈分为应用层(Application)、传输层(Transport)、网络层(Network)和链路层(Link)四层。如下图所示:

  1. 应用层(Application Layer):应用层是最靠近用户的一层,它提供了用户与网络之间的接口。在应用层,应用程序可以通过使用各种协议(如HTTP、FTP、SMTP等)来进行通信。应用层的主要任务是处理应用程序之间的通信和数据交换。
  2. 传输层(Transport Layer):传输层负责在网络中的两个主机之间提供可靠的端到端数据传输。传输层使用两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP)。TCP提供可靠的、面向连接的数据传输,而UDP提供不可靠的、无连接的数据传输。
  3. 网络层(Network Layer):网络层负责在网络中的不同主机之间提供数据包的传输和路由。网络层使用Internet协议(IP)来标识和定位主机,并使用路由协议来选择最佳路径将数据包从源主机发送到目标主机。
  4. 链路层(Link Layer):链路层负责在物理网络中传输数据帧。它处理与物理介质的接口,如以太网、Wi-Fi等。链路层还负责错误检测和纠正,以确保数据的可靠传输。

        这四个层次相互依赖,每个层次都有自己的协议和功能。通过TCP/IP协议栈,不同的应用程序可以在网络上进行通信,并实现数据的可靠传输和路由。

        一般在应用开发过程中,讨论最多的是TCP/IP模型。两台计算机通过TCP/IP协议通讯的过程如下所示:

        TCP是一种面向连接的、可靠的协议,有点像打电话,双方拿起电话互通身份之后就建立了连接,然后说话就行了,这边说的话那边保证听得到,并且是按说话的顺序听到的,说完话挂机断开连接。也就是说TCP传输的双方需要首先建立连接,之后由TCP协议保证数据收发的可靠性,丢失的数据包自动重发,上层应用程序收到的总是可靠的数据流,通讯之后关闭连接。

套接字编程基础

        socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

        在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

TCP/IP通信相关API函数

socket()

功能
	创建一个套接字,用于后续的通信操作
头文件
	#include <sys/types.h> /* See NOTES */
	#include <sys/socket.h>
原型
	int socket(int domain, int type, int protocol);
参数
  domain:协议族,常用的是AF_INET(IPv4)或AF_INET6(IPv6)。
  type:套接字类型,常用的是SOCK_STREAM(流套接字)或SOCK_DGRAM(数据报套接字)。
  protocol:协议,通常为0,表示使用默认协议。
返回值
	成功 套接字文件描述符
  失败 -1

bind()

功能
	 将套接字与指定的地址和端口绑定,使套接字能够监听指定的地址和端口。
头文件
  #include <sys/types.h> /* See NOTES */
  #include <sys/socket.h>
原型
	int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
  sockfd:套接字描述符。
  addr:指向要绑定的地址结构体的指针。
  addrlen:地址结构体的长度。
返回值
	成功 0
  失败 -1
 
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;	//地址类型
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);	//本机ip地址
servaddr.sin_port = htons(6666);	//端口号

listen()

功能
	设置监听上限,服务器的最大链接数
头文件
  #include <sys/types.h> /* See NOTES */
  #include <sys/socket.h>
原型
	int listen(int sockfd, int backlog);
参数
	sockfd:套接字描述符。
	backlog:连接请求队列的最大长度。
返回值
	成功 0
  失败 -1

accept()

功能
	接受客户端的连接请求,返回一个新的套接字用于与客户端进行通信
头文件
	#include <sys/types.h> 		/* See NOTES */
	#include <sys/socket.h>
原型
	int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
	sockfd:套接字描述符。
  addr:用于存储客户端地址的结构体指针。
  addrlen:addr结构体的长度。
返回值
	成功 新的套接字文件描述符,用于与客户端通信
  失败 -1

connect()

功能
	将客户端的套接字连接到服务器的套接字,建立通信连接
头文件
	#include <sys/types.h> 					/* See NOTES */
	#include <sys/socket.h>
原型
	int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
	sockfd:套接字描述符。
  addr:指向服务器地址结构体的指针。
  addrlen:地址结构体的长度。
返回值
	成功 0
  失败 -1

TCP/IP通信的C/S模型分析

        TCP/IP通信的C/S模型是指基于TCP/IP协议的客户端/服务器(C/S)模型,它是一种网络通信模型,通常用于实现网络中的应用程序之间的通信。在TCP/IP通信的C/S模型中,有两类主要角色:

  1. 客户端(Client): 客户端是请求服务的一方。它通常向服务器发送请求,并等待服务器的响应。客户端负责发起通信,并在与服务器建立连接后发送请求数据或命令。
  2. 服务器(Server): 服务器是提供服务的一方。它等待来自客户端的连接请求,并对这些请求进行处理。一旦接受了客户端的连接请求,服务器就可以与客户端进行通信,处理请求并提供相应的服务或数据。

        在C/S模型中,客户端和服务器通过网络进行通信。客户端与服务器之间的通信遵循一种请求-响应的模式,其中客户端发送请求,服务器接收并处理请求,然后返回响应给客户端。

C/S通信模型如下:

下图是基于TCP协议的客户端/服务器程序的一般流程:

        服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

        建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

        如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

        在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

C/S通信模型相关例程

例程分析

        在这个示例中,客户端会将命令行参数中的消息内容发送给服务器,服务器会将接收到的消息转换为大写并返回给客户端。客户端和服务器之间通过套接字进行通信,并使用TCP/IP协议来确保数据的可靠传输。

客户端代码:
  1. 创建一个套接字 ​​sockfd​​。
  2. 设置服务器地址和端口号,并与服务器建立连接。
  3. 将命令行参数中的消息内容存储到 ​​str​​ 变量中。
  4. 将消息内容通过套接字发送给服务器。
  5. 从服务器接收响应,并打印在控制台上。
  6. 关闭套接字。
服务器端代码:
  1. 创建一个套接字 ​​listenfd​​。
  2. 绑定服务器地址和端口号。
  3. 监听连接请求。
  4. 进入循环,等待客户端连接。
  5. 接受客户端连接,获取客户端的地址和端口号。
  6. 从客户端接收消息,并打印客户端的地址和端口号。
  7. 将接收到的消息转换为大写,并通过套接字发送给客户端。
  8. 关闭连接。

例程分享

客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;
char *str;

	if (argc != 2) {
		fputs("usage: ./client message\n", stderr);
		exit(1);
	}
str = argv[1];

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	write(sockfd, str, strlen(str));

	n = read(sockfd, buf, MAXLINE);
	printf("Response from server:\n");
	write(STDOUT_FILENO, buf, n);
	close(sockfd);

	return 0;
}
服务器端
#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 MAXLINE 80
#define SERV_PORT 6666

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	listen(listenfd, 20);

	printf("Accepting connections ...\n");
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
		n = read(connfd, buf, MAXLINE);
		printf("received from %s at PORT %d\n",
		inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
		ntohs(cliaddr.sin_port));
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);
		write(connfd, buf, n);
		close(connfd);
	}
	return 0;
}

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法直接为您提供完整的协议栈代码。协议栈的实现可能因硬件和操作系统不同而有所不同,需要根据具体情况进行适当的修改。 以下是一些常用协议栈的简介及其相关资源,供您参考: 1. WIFI协议栈:WIFI协议栈主要包括PHY层、MAC层和网络层。常用的WIFI协议包括IEEE 802.11a/b/g/n/ac/ax等。如果您需要实现WIFI协议栈,您可以参考以下资源: - ESP8266 WIFI模块源代码:https://github.com/espressif/esp8266-rtos-sdk/tree/master/components/esp8266/include/driver/include/driver - ESP32 WIFI模块源代码:https://github.com/espressif/esp-idf/tree/master/components/esp_wifi - Linux下的WIFI实现:https://wireless.wiki.kernel.org/en/developers/documentation/wireless-drivers 2. 蓝牙协议栈:蓝牙协议栈主要包括PHY层、MAC层、L2CAP层、RFCOMM层、SDP层等。常用的蓝牙协议包括BLE、Classic Bluetooth等。如果您需要实现蓝牙协议栈,您可以参考以下资源: - BlueZ蓝牙协议栈:https://git.kernel.org/pub/scm/bluetooth/bluez.git/ - Android蓝牙协议栈:https://android.googlesource.com/platform/system/bt/ - Nordic Semiconductor的nRF5 SDK:https://www.nordicsemi.com/Software-and-Tools/Software/nRF5-SDK 3. TCP/IP协议栈:TCP/IP协议栈主要包括物理层、数据链路层、网络层、传输层和应用层。常用的TCP/IP协议包括TCP、UDP、IP等。如果您需要实现TCP/IP协议栈,您可以参考以下资源: - lwIP协议栈:http://www.nongnu.org/lwip/ - Linux内核中的TCP/IP协议栈实现:https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt - Contiki OS中的TCP/IP协议栈实现:https://github.com/contiki-os/contiki/tree/master/os/net 4. CoAP协议栈:CoAP协议栈是一种轻量级的RESTful协议,适用于物联网设备之间的通信。如果您需要实现CoAP协议栈,您可以参考以下资源: - Erbium CoAP协议栈:https://github.com/contiki-os/er-coap - LibCoAP协议栈:https://github.com/obgm/libcoap - Californium CoAP协议栈:https://github.com/eclipse/californium 5. MQTT协议栈:MQTT协议栈是一种轻量级的消息协议,适用于物联网设备之间的通信。如果您需要实现MQTT协议栈,您可以参考以下资源: - Paho MQTT协议栈:https://github.com/eclipse/paho.mqtt.embedded-c - Mosquitto MQTT协议栈:https://github.com/eclipse/mosquitto - Eclipse IoT MQTT协议栈:https://www.eclipse.org/paho/clients/c/embedded/ 6. HTTP(S)协议栈:HTTP(S)协议栈是一种广泛应用于互联网上的应用层协议。如果您需要实现HTTP(S)协议栈,您可以参考以下资源: - Mongoose HTTP(S)协议栈:https://github.com/cesanta/mongoose - libcurl HTTP(S)协议栈:https://curl.se/libcurl/ - Microchip HTTP(S)协议栈:https://www.microchip.com/design-centers/wireless-connectivity/wifi/products/wi-fi-software/mrf24w-software 7. SSL/TLS协议栈:SSL/TLS协议栈是一种安全传输协议,适用于互联网上的安全通信。如果您需要实现SSL/TLS协议栈,您可以参考以下资源: - OpenSSL SSL/TLS协议栈:https://www.openssl.org/ - wolfSSL SSL/TLS协议栈:https://www.wolfssl.com/ - mbed TLS SSL/TLS协议栈:https://tls.mbed.org/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值