目录
前言
本文总结了若干博主学习UNXI网络编程时遇到的疑惑点,以及我个人查找资料后得出的解答,希望对你有所帮助。
1. socket_fd(套接字描述符),socket(套接字),sockaddr_in(套接字地址结构)三者是什么样的关系?
socket: 通信的端点,单纯的socket没有任何信息,只表明在计算机上开了个用于通信的口子。把通信抽象成两个点以及连接两个点的直线,socket就是其中的一个点。
sockaddr_in 给socket赋值的,也就是赋予socket所代表端点的各种属性:IP,端口,协议等。sockaddr_in与socket之间的关系是合作关系,不是包含关系!创建socket后,想要利用它通信的话,就必须赋予它属性,之后便称作两者关联了起来。sockaddr_in与sockaddr的关系看这篇文章:sockaddr和sockaddr_in详解
socket_fd 套接字描述符,通信过程中很多函数都会返回socket_fd用以唯一描述socket。socket_fd与unix系统中的文件描述符很像,本质上都是指向的系统为进程划分出来的缓冲空间,每个socket_fd指向两块缓冲空间:输入缓冲区recvBuf和输出缓冲区sendbuf。详见一文彻底搞通TCP之send & recv原理
三者关系碎碎念 定义出socket就是为了通信的,而socket内核只会读/写缓冲区,具体的传输工作还是传输层基于缓冲区完成的,所以用socket_fd可以指向recvBuf和sentBuf,来唯一表示socket。通信过程中需要的对端地址,端口,协议等信息则储存在sockaddr_in中,可以通过*bind()*绑定socket和sockaddr_in,在connect,accept等函数中,系统也会自动绑定。
2. getsockname() 和 getpeername()的使用方法
getsockname(int sock_fd,struct sockaddr *localaddr, socketlen_t *addrlen)
返回与套接字相关联的本地协议地址。
getsockname(int sock_fd,struct sockaddr *localaddr, socketlen_t *addrlen)
返回与套接字相关联的外地协议地址。具体使用方法参考linux getsockname和getpeername使用
3. recv()和send()中的socket_fd到底填什么?
这个问题是上面这些问题的起点,
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
recv()和send()函数中第一个参数要求填一个socket_fd,那么这个socket填哪一个呢?官方的解释是标识连接的套接字的描述符
,这是一个不那么清晰的解释。好了,长话短说,举几个例子解释这个概念:(注意,不是完整代码)
/************客户端例子***************/
// 定义一个socketaddr_in,还记得吗?socket地址结构
struct sockaddr_in serv_addr;
// 定义一个socket
int sock_fd;
sock_fd=socket(AF_INET,SOCK_STREAM,0)
//填充数据,sockaddr_in填充的是服务器的信息
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
// ->point1
// 连接客户端和服务端
connect(socket,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))
// ->point2
//...
//发送和接受函数
send(sock_fd,...)
recv(sock_fd,...)
上面是一个简单的客户端部分程序,而我们的标识连接的套接字的描述符
就就是connect
后的socket_fd,那connect
之前和之后的socket_fd 有啥区别呢?利用2中的getsockname() 和 getpeername()打印point1
和point2
处的与socket_fd 相关联本地和外部地址得到:
# point1
localaddr: 0.0.0.0:0
peeraddr: 0.0.0.0:0
# point2
localaddr: 127.0.0.1:45670
peeraddr: 127.0.0.1:64399
这应该就很清楚了标识连接的套接字的描述符
是啥意思了。
简单讲一下服务器端(注意,也不是完整代码)
/**********服务器端例子**************/
// 定义用于储存服务器和客户端的地址结构(体)
struct sockaddr_in server_sockaddr,client_sockaddr;
// 定义两个socket
int server_fd,conn_fd;
socket_fd=socket(AF_INET,SOCK_STREAM,0)
// 设置server_sockaddr的属性
server_sockaddr.sin_family = AF_INET;//IPv4
server_sockaddr.sin_port = htons(SERVPORT);//端口
server_sockaddr.sin_addr.s_addr = INADDR_ANY;//本主机的任意IP都可以使用
bzero(&(server_sockaddr.sin_zero),8);//填充0
// 将服务的地址结构和套接字绑定
bind(server_fd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))
// 接受客户端连接请求,并将返回值赋值给conn_fd
conn_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)
//...
send(conn_fd,...)
recv(conn_fd,...)
上述代码中conn_fd是我们说的标识连接的套接字的描述符
,同样输出server_fd和conn_fd的相关联的本地和外部地址:
# server_fd
localaddr: 0.0.0.0:64399
peeraddr: 0.0.0.0:0
# conn_fd
localaddr: 127.0.0.1:64399
peeraddr: 127.0.0.1:45670
相信应该解释清楚了。
为什么不用server_fd接受accept的返回值? server_fd还要用于监听其他客户端的连接请求。服务器没每ccept一个客户端请求,便开一个子进程处理该客户端的请求。