从上篇文章了解到了网络基础知识,那么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()关闭套接字。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#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.编写客户端程序
客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#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:
cpp
ifconfig eth0 192.168.5.9
就可以发现开发板的IP已经变成了192.168.5.9。
上机测试:
将客户端和服务端的程序编译:
将ubuntu作为客户端,开发板作为服务端,
注意:客户端的程序文件最后在ubuntu上运行,所以不能用交叉编译工具,而是用gcc指令,因为交叉编译工具编译完,就代表你这个文件需要在其他环境进行运行了。
利用scp指令将服务端文件传至开发板
先开启服务器,在开发板执行服务端文件:
ubuntu执行客户端:
连接成功
客户端发送字符串给服务端,服务端可以解收,成功!