socket套接字编程

匿名 (未验证) 提交于 2019-12-02 22:56:40

网络基础概念
IP地址:在IP协议中用来标识网络中不同主机的地址;

IPv4:IP地址是一个4字节,32位的整数;
IPv6是一个16字节,128位的整数。

端口号: 用来标识网络中唯一的一个网络服务进程,一个端口号只能被一个进程占用

一个进程可以绑定多个端口号,但是一个端口号只能被一个进程绑定

网络字节序:
发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接受的字节依次保存在接收缓冲区中,按内存地址从低到高顺序保存;
所以,网络字节流的地址应该这样规定:先发出的数据是低地址,后发出去的数据是高地址。
TCP/IP协议规定网络数据流应采用大端字节序,即低地址高字节,不管这台主机是大端还是小端,都需要按照TCP/IP规定的网络字节序接受/发送数据。
如果当前主机是小端,需要先将数据转换成大端,否则忽略直接发送。

为提高网络程序的可移植性,使同样的C代码在大端机和小端机上编译后都能正常运行,可以调用库函数做网络字节序和主机字节序之间的转换。

#include<arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); 注:h:host n:network l:long32位长整型 s:short16位短整型

socket套接字
sockaddr结构
socket网络编程接口中表示socket地址的是结构体sockaddr
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,但是各种网络的地址格式并不相同。

Ipv4和Ipv6地址格式定义在netinet/in.h中,Ipv4和Ipv6地址类型分别定义为常数AF_INET,AFINET6,只要取得某种sockaddr结构体的首地址就可以根据地址类型字段确定结构体中的内容。

socket编程接口
#include<sys/types.h>
#include<sys/socket.h>

创建socket文件描述符
int socket(int domain,int type,int protocol)
参数:
domain参数告诉系统使用的是哪个底层协议族(对于TCP/IP协议族而言,该参数应该设置为AF_INET用于Ipv4,AF_INET6用于Ipv6;对于Unix本地域协议族而言,该参数应该设置为AF_UNIX);

type参数指定服务类型,服务类型主要有SOCK_STREAM服务(流服务),SOCK_UGRAM服务(数据报);现在type参数可以接受上述两种服务类型和下面两个标志相与的值:SOCK_NONBLOCK,SOCK_CLOEXEC,它们分别表示将新创建的socket设为非阻塞的,以及用fork调用创建子进程时在子进程中关闭该socket。
对YCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_UGRAM表示传输层使用UDP协议;

protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议,几乎在所有情况下都应该把它设置为0,表示使用默认的协议。
返回值:系统调用成功时返回一个socket描述符,失败时返回-1,并设置erron。

创建socket的时候指定了地址族,但是未指定使用地址族中的哪个具体的socket地址,将一个socket和一个socket地址绑定称为给socket命名。
服务器程序所监听的网络地址和端口号通常是固定的,客户端程序得知服务器程序的地址和端口号后向服务器发起连接,服务器调用bind函数绑定一个固定的端口号等待客户端与这个端口号建立连接

#include<sys/types.h>
#include<sys/socket.h>
绑定端口号
int bind(int sockfd,const struct sockaddr* address,socklen_t address_len)
参数:
bind将address指向的socket地址分配给未命名的socketfd文件描述符,address_len指出了socket地址的长度
返回值:成功返回0,失败返回-1并设置erron
其中两种常见的erron:
EACCES:被绑定的地址是受保护的地址,只能超级用户访问;
EADDRINUSE:被绑定的地址正在使用中。

从指定地址接收UDP数据报并保存数据发送源地址
int recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_from,socklen_t *src_len)
参数:
sockfd:标识一个已绑定的端口号;
buf:接收数据的缓冲区;
len:缓冲区长度;
flags:调用操作方式,一般设置为0;
src_from:指向装有数据源地址的缓冲区;
src_len:指向装有数据源地址缓冲区的长度值
返回值:
成功返回接收到的字符数,失败返回-1并设置erron。

把UDP数据报发给指定IP端口地址
int sendto(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_to,socklen_t src_len)
参数:
sockfd:socket描述符;
buf:UDP数据报缓存地址;
len:UDP数据报长度;
flags:调用操作方式,一般设置为0;
src_to:UDP数据报发送到哪个IP端口;
src_len:对方地址的长度
返回值:
返回实际传送出去的字符数,失败返回-1设置erron。

socket被命名之后,还不能马上接受客户连接,还需要创建一个监听队列以存
放待处理的客户连接
#include<sys/spcket.h>
开始监听socket
int listen(int sockfd,int backlog)
**参数:**sockfd:指定被监听的socket;
backlog:提示内核监听队列的最大长度(监听队列长度如果超过backlog,服务器将不再受理新的客户连接,客户端将收到ECONNREFUSED错误信息)。
返回值:成功返回0,失败返回-1并设置erron。

三次握手完成后,从listen监听队列中接受一个连接,如果调用了accept后还没有客户端连接请求就阻塞式的进行等待
#include<sys/types.h>
#include<sys/socket.h>
接收请求
int accept(int sockfd,struct sockaddr* address,socklen_t address_len)
**参数:**sockfd:执行过listen系统调用监听的socket;
address:输出参数,用来获取被接受连接的远端的socket的地址,NULL表示不关心客户端的地址;
address_len:传入传出参数,传入的是缓冲区address的大小,传出的是客户端地址结构体的实际长度
返回值:调用成功时返回一个新的连接的socket,该socket唯一的标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接的对应的客户端通信;失败返回-1并设置erron。

服务器通过listen调用被动的接受连接,客户端可以调用connect系统调用接口主动的与服务器建立连接
建立连接
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen)
参数: sockfd:由socket系统调用返回一个socket;
addr:服务器监听的socket地址;
addrlen:socket地址的长度。
返回值:成功返回0,一旦成功建立连接,sockfd就唯一的表示了这个连接,客户端就可以通过读写sockfd来与服务器建立连接;失败返回0并设置erron。
其中两种常见的erron:
ECONNREFUSED:目标端口不存在,连接被拒绝;
ETIMEDOUT:连接超时。

地址转换函数:
基于Ipv4的socket网络编程,sockaddr_in中的成员struct in_addr.s_addr表示32位的IP地址(4字节IP地址),但是通常使用的是点分十进制的字符串表示的IP地址,下述函数可以在4字节IP地址和点分十进制IP地址之间进行转换。

将点分十进制字符串IP地址转换成32位的网络序列IP地址(in_addr):
#include<arpa/inet.h>
int inet_aton(const char* strptr,struct in_addr* addrptr)
参数:strptr:输入参数strptr是点分十进制字符串IP地址;
addrptr:输出参数addrptr用新的IP地址更新的结构(将转换后的IP地址保存在其中)
返回值:函数调用成功返回非0,失败返回0并设置erron

in_addr_t inet_addr(const char* strptr)
参数:strptr:点分十进制字符串IP地址
返回值:调用成功返回网络字节序二进制值,失败返回-1
缺陷:转换不了255.255.255.255这个有效IP地址
int inet_pton(int family,const char* strptr,void* addrptr)
参数:family:所属的协议族,AF_INET或AF_INET6
strptr:点分十进制字符串IP
addrptr:存放转换成功的32位网络字节序列
返回值:成功返回1,地址无效返回0,调用失败返回-1
32位的网络序列IP地址(in_addr)转换成点分十进制字符串IP地址:
char* inet_ntoa(struct in_addr inaddr)
参数:inaddr:32位的网络序列IP地址
返回值:成功返回指向点分十进制字符串IP的指针
注意:该字符串的空间为静态存储区,不需要手动释放,但是在第二次调用该函数时,上一次结果会被覆盖。
const char* inet_ntop(int family,const void* addrptr,char* strptr,size_t len);
参数:family:所属的协议族
addrptr:32位网络字节序列IP
strptr:不能是一个空指针,存放转换成功的点分十进制字符串IP
len:目标存储单元的大小
返回值:调用成功返回strptr指针,出错返回NULL

文章来源: socket套接字编程
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!