imx6ull/linux应用编程学习(11)SOCKET
从上篇文章了解到了网络基础知识,那么socket与tcp/ip、udp等有什么关系呢?
imx6ull/linux应用编程学习(10)网络基础知识(基于正点原子)-CSDN博客
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后
Socket是计算机网络中的一个通信端点,可以视为网络通信的基础设施,它为不同的网络通信协议提供了统一的接口。通过它,程序可以发送和接收数据。Socket的主要作用是建立网络连接,并进行数据传输。
Socket的类型
-
流式套接字(Stream Socket):基于TCP(Transmission Control Protocol)协议的Socket,提供面向连接的、可靠的数据传输服务。数据在传输过程中不会丢失,并且接收方会以发送方相同的顺序接收数据。
-
数据报套接字(Datagram Socket):基于UDP(User Datagram Protocol)协议的Socket,提供无连接的、尽力而为的数据传输服务。数据可能会在传输过程中丢失,接收顺序也可能不同。
由上可知,socket根据作为udp和tcp的接口,可发为以上两类,在进行更详细对比之前,我们先回顾一下tcp与udp的区别。
TCP/IP与UDP的关系
TCP/IP和UDP是两种不同的传输层协议:
-
TCP/IP协议:
- 面向连接:在发送数据之前需要建立连接(三次握手)。
- 可靠传输:通过确认机制和重传机制确保数据的可靠传输。
- 流控制:可以调整发送数据的速率,以避免网络拥塞。
- 数据顺序:确保数据按照发送顺序接收。
-
UDP协议:
- 无连接:发送数据时不需要建立连接,传输效率高。
- 不保证可靠性:没有数据确认机制,数据可能丢失或重复。
- 无序传输:数据报的接收顺序可能与发送顺序不同。
- 适合实时应用:如视频流、语音通话等对数据传输速度要求高,但对数据丢失不敏感的应用。
(tips:tcp和udp都依赖IP,ip就相当于每台主机的身份证,tcp和udp需要根据ip去辨别不同的主机)
Socket与TCP/IP、UDP的关系
Socket是应用层与传输层协议(如TCP、UDP)之间的桥梁。通过Socket编程,开发者可以选择使用TCP或UDP协议来实现具体的网络通信功能。
-
使用TCP的Socket:
- 服务器端:创建一个流式Socket -> 绑定端口 -> 监听连接请求 -> 接受连接 -> 读写数据。
- 客户端:创建一个流式Socket -> 连接服务器 -> 读写数据。
-
使用UDP的Socket:
- 服务器端:创建一个数据报Socket -> 绑定端口 -> 接收和发送数据报。
- 客户端:创建一个数据报Socket -> 发送和接收数据报。
总结
Socket是实现网络通信的基本单元,它提供了统一的接口,方便程序员使用TCP或UDP进行数据传输。通过选择不同类型的Socket,程序可以在可靠性、速度和资源利用之间进行权衡,以满足不同应用场景的需求。
引用网上的一张图:
socket常用API:
1. 创建Socket
- socket()
- 原型:socket(socket_family, socket_type, protocol=0)
- 描述:创建一个新的Socket。
- 参数:
- socket_family:指定地址族,常用的有AF_INET(IPv4)、AF_INET6(IPv6)。
- socket_type:指定Socket类型,常用的有SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)。
- protocol:一般设为0,表示使用默认协议。
2. 绑定地址
- bind()
- 原型:bind(address)
- 描述:将Socket绑定到指定的地址(IP地址和端口)。
- 参数:
- address:一个元组,包含IP地址和端口号,如('192.168.1.1', 8080)。
3. 监听连接
- listen()
- 原型:listen(backlog)
- 描述:使Socket进入监听状态,准备接受连接请求。
- 参数:
- backlog:指定等待连接的最大数量。
4. 接受连接
- accept()
- 原型:accept()
- 描述:接受一个连接,返回一个新的Socket对象和客户端地址。
- 返回值:一个元组(conn, address),conn是新的Socket对象,address是客户端地址。
5. 连接到远程服务器
- connect()
- 原型:connect(address)
- 描述:连接到远程服务器。
- 参数:
- address:远程服务器的地址和端口号。
6. 发送数据
-
send()
- 原型:send(bytes)
- 描述:通过Socket发送数据。
- 参数:
- bytes:要发送的数据,类型为字节串。
-
sendto()
- 原型:sendto(bytes, address)
- 描述:通过Socket发送数据到指定地址(用于UDP)。
- 参数:
- bytes:要发送的数据,类型为字节串。
- address:目标地址和端口号。
7. 接收数据
-
recv()
- 原型:recv(bufsize)
- 描述:接收数据。
- 参数:
- bufsize:指定一次接收的最大数据量。
-
recvfrom()
- 原型:recvfrom(bufsize)
- 描述:接收数据并获取发送方地址(用于UDP)。
- 参数:
- bufsize:指定一次接收的最大数据量。
- 返回值:一个元组(data, address),data是接收到的数据,address是发送方地址。
8. 关闭Socket
- close()
- 原型:close()
- 描述:关闭Socket,释放资源。
socket 编程实战:
任务:实现一个简单地服务器和一个简单地客户端应用程序,将ubuntu和开发板分别作为客户端和服务端,实现交互
1.服务器程序:
编写服务器应用程序的流程如下:
①、调用 socket()函数打开套接字,得到套接字描述符;
②、调用 bind()函数将套接字与 IP 地址、端口号进行绑定;
③、调用 listen()函数让服务器进程进入监听状态;
④、调用 accept()函数获取客户端的连接请求并建立连接;
⑤、调用 read/recv、 write/send 与客户端进行通信;
⑥、调用 close()关闭套接字。
#include #include #include #include #include #include #include #include #define SERVER_PORT 8888 //端口号不能发生冲突,不常用的端口号通常大于5000 int main(void) { struct sockaddr_in server_addr = {0}; struct sockaddr_in client_addr = {0}; char ip_str[20] = {0}; int sockfd, connfd; int addrlen = sizeof(client_addr); char recvbuf[512]; int ret; /* 打开套接字,得到套接字描述符 */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (0 > sockfd) { perror("socket error"); exit(EXIT_FAILURE); } /* 将套接字与指定端口号进行绑定 */ server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (0 > ret) { perror("bind error"); close(sockfd); exit(EXIT_FAILURE); } /* 使服务器进入监听状态 */ ret = listen(sockfd, 50); if (0 > ret) { perror("listen error"); close(sockfd); exit(EXIT_FAILURE); } /* 阻塞等待客户端连接 */ connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen); if (0 > connfd) { perror("accept error"); close(sockfd); exit(EXIT_FAILURE); } printf("有客户端接入...\n"); inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str)); printf("客户端主机的IP地址: %s\n", ip_str); printf("客户端进程的端口号: %d\n", client_addr.sin_port); /* 接收客户端发送过来的数据 */ for ( ; ; ) { // 接收缓冲区清零 memset(recvbuf, 0x0, sizeof(recvbuf)); // 读数据 ret = recv(connfd, recvbuf, sizeof(recvbuf), 0); if(0 >= ret) { perror("recv error"); close(connfd); break; } // 将读取到的数据以字符串形式打印出来 printf("from client: %s\n", recvbuf); // 如果读取到"exit"则关闭套接字退出程序 if (0 == strncmp("exit", recvbuf, 4)) { printf("server exit...\n"); close(connfd); break; } } /* 关闭套接字 */ close(sockfd); exit(EXIT_SUCCESS); }
客户端连接到服务器之后,客户端会向服务器(也就是本程序)发送数据,在我们服务器应用程序中会读取客户端发送的数据并将其打印出来,
SERVER_PORT 宏指定了本服务器绑定的端口号,这里我们将端口号设置为 8888,端口不能与其它服务器的端口号发生冲突,不常用的端口号通常大于 5000。
2.编写客户端程序
客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入
#include #include #include #include #include #include #include #include #define SERVER_PORT 8888 //服务器的端口号 #define SERVER_IP "192.168.5.9" //服务器的IP地址 int main(void) { struct sockaddr_in server_addr = {0}; char buf[512]; int sockfd; int ret; /* 打开套接字,得到套接字描述符 */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (0 > sockfd) { perror("socket error"); exit(EXIT_FAILURE); } /* 调用connect连接远端服务器 */ server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); //端口号 inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP地址 ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (0 > ret) { perror("connect error"); close(sockfd); exit(EXIT_FAILURE); } printf("服务器连接成功...\n\n"); /* 向服务器发送数据 */ for ( ; ; ) { // 清理缓冲区 memset(buf, 0x0, sizeof(buf)); // 接收用户输入的字符串数据 printf("Please enter a string: "); fgets(buf, sizeof(buf), stdin); // 将用户输入的数据发送给服务器 ret = send(sockfd, buf, strlen(buf), 0); if(0 > ret){ perror("send error"); break; } //输入了"exit",退出循环 if(0 == strncmp(buf, "exit", 4)) break; } close(sockfd); exit(EXIT_SUCCESS); }
SERVER_IP 和 SERVER_PORT 指的是服务器的 IP 地址和端口号,服务器的 IP 地址根据实际情况进行设置,服务器应用程序示例代码 30.4.1 中我们绑定的端口号为 8888,所以在客户端应用程序中我们也需要指定 SERVER_PORT 为 8888。
注意:
客户端和服务端要确定可以互ping,我ubuntu的ip是192.168.5.12,子网掩码是255.255.255.0,那么程序里面的ip就应该是192.168.5.x。我这里设为了192.168.5.9。
然后再开发板界面,设置临时ip:
ifconfig eth0 192.168.5.9
就可以发现开发板的IP已经变成了192.168.5.9。
上机测试:
将客户端和服务端的程序编译:
将ubuntu作为客户端,开发板作为服务端,
注意:客户端的程序文件最后在ubuntu上运行,所以不能用交叉编译工具,而是用gcc指令,因为交叉编译工具编译完,就代表你这个文件需要在其他环境进行运行了。
利用scp指令将服务端文件传至开发板
先开启服务器,在开发板执行服务端文件:
ubuntu执行客户端:
连接成功
客户端发送字符串给服务端,服务端可以解收,成功!