Linux网络编程(socket编程)各类函数、编程步骤、原理详解

本文详细介绍了Socket编程的基础知识,包括socket(), bind(), listen(), accept(), connect()等关键函数的使用方法,以及socket通信流程、网络字节顺序、IP地址转换等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、socket()函数

int socket(int domain,int type,int protocol);

socket()中domain用于指定通信域参数,选择通信时的地址族,有两个选项AF_INET和AF_UNIX,这些地址族都在sys/socket.h中定义;其中AF_INET用于网络通信,使用IPv4格式的地址;AF_UNIX用于本地进程通信;

type参数用于指定socket的类型,有三个选项SOCK_STREAM,SOCK_DGRAM,SOCK_RAW;SOCK_STREAM表示使用TCP协议,SOCK_DGRAM表示使用UDP协议。SOCK_RAW表示使用ICMP协议,提供单一的网络访问,一般用于开发人员需要自行设置数据报格式和参数时;

protocol参数一般设置为0,表示使用默认协议;

socket函数调用失败会返回-1,并设置error;

2、bind()函数

int bind(int sockfd,const struct sockaddr * addr,socklen_t addrlen);

bind()函数用于服务器端,服务器的网络地址和端口号通常固定不变,客户端得知服务器的地址和端口号以后,可以主动向服务器请求连接。因此服务器需要调用bind()绑定地址。bingd()函数定义在sys/socket.h头文件中;

sockfd表示socket文件的文件描述符,一般为socket函数的返回值;

addr表示服务器的通信地址,本质为struct sockaddr 结构体类型指针,struct sockaddr结构体定义如下

struct sockaddr{
	sa_family_t sa_family;
	char		sa_data[14];
};

结构体中的成员,sa_data[]表示进程地址;

bind函数中的第三个参数addrlen表示参数addr的长度;addr参数可以接受多种类型的结构体,而这些结构体的长度各不相同,因此需要使用addrlen参数额外指定结构体长度;可以使用下列语句,定义一个struct sockaddr_in类型的结构体

struct sockaddr_in servaddr;//结构体定义
bzero(&servaddr,sizeof(servaddr));//结构体清零
servaddr.sin_family=AF_INET;//设置地址类型为AF_INET
servaddr.si_addr.s_addr=htonl(INADDR_ANY);//设置网络地址为INADDR_ANY
servaddr.sin_port=htons(80);//设置端口为80

bind函数调用成功返回0,否则返回-1,并设置erro;

3、关于bind()函数中sockaddr_int结构体的详解
https://blog.youkuaiyun.com/will130/article/details/53326740/

4、listen()函数

int listen(int sockfd,int backlog);

listen()函数用于服务器,使已绑定的socket等待监听客户端的连接请求,并设置服务器同时可连接的数量,listen函数位于sys/socket.h头文件中;

sockfd为socket文件描述符;

backlog用于设置请求队列最大长度;

listen函数调用成功返回0,否则返回-1;

5、accept()函数

int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);

sockfd的参数为listen()函数返回的监听套接字;

addr是一个传出参数,表示客户端的地址,该参数设置为NULL时,表示不关心客户端的地址。

addrlen为一个传入传出参数,传入时为函数调用时提供参数addr的长度,传出时为客户端地址结构体的实际长度;

accept的返回值也是一个套接字,该套接字用于与本次通信的客户端进行数据交互。

6、connect()函数

int connect(int sockfd, const struct sockaddr * addr,socklen_t addrlen);

connect()函数用于客户端,该函数的功能为向服务器发起连接请求。connect()函数存在与sys/socket.h头文件中。

connect函数的参数与bind()函数中的参数形式一致;

7、send()函数

ssize_t send(int sockfd,const void * buf,size-t len,int flags);

send()函数用于向处于连接状态的套接字中发送数据,该函数粗在于函数库sys/socket.h中;

sockfd表示要发送数据的socket文件描述符;

buf为指向要发送数据的缓冲区指针;

len表示缓冲区buf中要发送的数据的长度;

flags参数为调用的执行方式(阻塞/非阻塞),当flags设置为0时,可以使用函数write()来代替send()函数;

8、sendto()、sendmsg()函数

size_t sendto(int sockfd,const void * buf,size_t len,int flags,const struct sockaddr * dest_addr,socklen_t addrlen);

size_t sendmsg(int sockfd,const struct msghdr * msg,int flags);

sendto()函数中的前四个参数都和send()的参数相同,后面两个参数分别用于设置接收数据进程的地址和地址长度;

sendmsg()函数中的第二个参数msg为struct msghdr类型的结构体指针,该参数用于传入目标继承的地址,地址的长度等信息;

若sendto()函数和sendmsg()函数向已连接的进程中发送消息,则忽略参数dest_addr、addrlen和msg结构体中用于传递地址的成员。此时若参数dest_addr和addrlen不为NULL,则可能会返回错误EISCONN或0;

这两个函数调用成功返回0,失败返回-1,并设置errno;

函数调用成功并不代表接收端一定能接受到数据;

9、recv()、recvfrom()、recvmsg()函数详解

size_t recv(int sockfd,void * buf,size_t len,int flags);

recv()函数的参数列表与send()函数的参数列表形式相同,代表的含义也基本对应,只是参数sockfd表示用于接收数据的socket文件描述符;

此外函数read()、recvfrom()、recvmsg()函数也可用于接收信息,recvfrom、recvmsg函数与sendto、sendmsg相对;

这几个函数调用成功将返回接收到的字节数,调用失败将返回-1并设置errno;

10、close()函数

int close(int fd);

close()函数存在于函数库unistd.h函数库中;

close()函数用于释放系统分配给套接字的资源,该函数即文件操作中常用的close函数。

参数fd为需要关闭的套接字文件描述符;

调用成功返回0,否则返回-1并设置errno;

11、socket通信流程

根据进程在网络通信中使用的协议,可以将socket通信方式分为两种:面向连接、基于TCP协议的通信;另一种是面向无连接,基于UDP协议的通信。

当使用面向连接的方式进行通信时,服务器和客户机先各自创建socket文件,服务器调用bind()函数绑定服务器端口和地址。之后服务器通过接口listen()设置可连接的数量。若客户端需要与服务器进行交互,客户端会调用connect()函数向已知服务器地址端口发送连接请求并 阻塞 等待服务器应答。服务器监听到请求连接以后,会调用accept()函数试图进行连接。若服务器为到达最大连接数量,便成功建立连接,此后客户端解除阻塞,两端可正常通信。否则服务器忽略本次连接。最后当通信完成以后,双方各自调用close()函数,关闭socket文件,释放资源。

当使用面向无连接的方式进行通信的时候,服务器和客户机各自创建自己的socket文件,再由服务器调用bind()函数绑定服务器地址和端口。此后通信双方可以开始通信,需要注意的,因为服务器与客户机尚未建立连接,所以客户端每次向服务器发送数据时,都需要额外的指定服务器的地址端口。同样的,若服务器需要向客户端发送数据,服务器也需要额外指定地址端口。通信结束以后,通信双方需要调用close()函数,关闭socket文件,释放资源。

12、网络字节顺序

高字节和低字节

一. 计算机的数值应视为连续若干个二进制位的集合;

二. 所谓高、低字节就是此集合中位地址高/低的二进制位集合;

三. 例如定义一个unsigned short型变量在0x1234 5678,那么这个变量的地址就是0x1234 5678,占用0x1234 5678与0x1234 5679两字节存储空间,其中0x1234 5678是低字节、0x1234 5679是高字节。

四、 一个16进制数有两个字节组成,例如:A9。

高字节就是指16进制数的前8位(权重高的8位),如上例中的A。

低字节就是指16进制数的后8位(权重低的8位),如上例中的9。

大端模式:将数据的高字节保存在内存的低地址,将数据的低字节保存在内存的高地址,这种存放模式称为大端模式;

小端模式:将数据的高字节保存在内存的高地址,将数据的低字节保存在内存的低地址,这种存放模式称为小端模式;

磁盘文件中的多字节数据相对于文件中的偏移地址有大端、小端之分,内存中的多字节数据相对于内存地址也有大端、小端之分;同样的,网络数据流同样有大端、小端之分;

发送端主机在发送数据时,通常将发送缓冲区中的数据按内存地址从低到高顺序依次发出;接收端主机从网络上接收数据时,会将从网络上接收的数据按内存地址从低到高的顺序依次保存。因此,网络数据流的地址这样规定:先发出的数据占据低地址,后发出的数据占据高地址;

TCP/IP协议规定,网络数据流应采用大端模式存储。举例说明:假设从网络获取了一个16位的数据0x1121,即十进制的4385,那么发送端主机在发送该数据时,会先发送低地址的数据0x21,再发送高地址的数据0x11;假设接收端为此数据分配的地址为0、1,高字节的数据0x11被存放到低地址0所对应的空间;低字节的数据0x21被存放到高地址1所对应的空间。

但是如果发送端主机采用小端模式,那么16位的数据会被解释为0x2111,这显然不正确。因此发送端主机将数据填充到发送缓冲区前要先进性字节顺序转换。

linux提供了一些字节顺序转换的函数,在函数库arpa/inet.h中

unit32_t htonl(unit32_t hostlong);
unit16_t htons(unit16_t hostshort);
unit32_t ntohl(unit32_t netlong);
unit16_t ntohs(unit16_t nettshort);

h代表主机
n代表网络
l代表32位长整型
s代表16位短整型

htonl 表示host to net long

若主机采用大端模式,则这些函数不转换,将参数原样返回。只有主机采用小端模式,参数的字节顺序才会转换

13、IP地址转换函数

常见的IP地址格式类似192.168.10.1,这是一个IPv4格式的地址,但是这种格式只是为了方便用户对其操作。若要计算机能够识别,需要先将其由文本格式转换为二进制格式。

早期linux系统中常用以下函数转换IP地址

int inet_aton(const char * cp,struct in_addr * inp);
in_addr_t inet_addr(const char * cp);
char * inet_ntoa(struct in_addr_in);

但是以上函数只能处理IPv4地址。如今linux使用以下函数进行IP地址转换;不仅能转换IPv4地址,也能转换IPv6地址;这些函数存在函数库arpa/inet.h中;

int inet_pton(int af,const char * src, void * dst);
const char * inet_ntop(int af,const void * src,char * dst,socklen_t size);

函数inet_pton()会先将字符串src转换为af地址族中的网络地址结构,进而将转换后的网络地址结构存储到参数dst所指的缓冲区中,其中参数af的值必须是AF_INET或AF_INET6。

函数inet_ntop()会将af地址族中的网络地质结构src转换为字符串,再将获得的地址字符串存储到参数dst所指的缓冲区中。

以上两个函数所需要转换IPv4和IPv6这两种形式的地址,因此用来传递地址的参数类型为void *;

14、sockaddr数据结构

IPv4和IPv6的地址格式定义在netinet/in.h中:IPv4地址用结构体sockaddr_in表示,该结构体中包含16位端口号和32位的IP地址;IPv6地址用结构体sockaddr_in6表示,该结构体中包含16位端口号和128位的IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用结构体sock_addr_un表示。

各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现都有长度字段,如Linux就没有),后16表示地址类型。IPv4、IPv6和UNIX Domain Socket的地址类型分别为AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sickaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。也是因为如此,socket API可以接收各种类型的sockaddr结构体指针作为参数,例如bind()、accept()等函数,这些函数应该设计成void* 类型以便接收各种类型的指针。单数sock API的实现遭遇ANSI C标准化,那时候还没有void* 类型,因此这些函数都用struct sockaddr * 类型表示,但是在传递参数之前要进行强制类型转换,如:

struct sockaddr_in servaddr;
bind(listen_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值