谈谈socket的使用

创建socket

在Linux中,所有东西都是文件。下面是创建一个socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);

domain参数告诉系统使用哪个协议族。
type参数指定服务类型。服务类型主要为SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务。对于TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议。
protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。一般设置为0,表示使用默认协议。
socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。

命名socket

将一个socket与socket地址绑定称为给socket命名。
在服务器程序中需要命名socket,只有命名后客户端才知道如何连接它。客户端则不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址。
命名socket的系统调用是bind

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr,socklen_t addlen);

bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。
bind成功时返回0,失败则返回-1并设置errono。其中常见的errno是EACCES和EADDRINUESS。分别代表被绑定的是受保护的地址,仅超级用户能够访问和被绑定的地址在使用中。

监听socket

socket被命名之后,还要开启监听,才能接收连接。

#include<sys/socket.h>
int listen(int sockfd, int backlog);

sockfd参数指定被监听的socket,backlog参数提示内核监听队列的最大长度。如果监听队列的长度超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。
listen成功时返回0,失败则返回-1并设置errno。

接受连接

accept从listen监听队列中接受一个连接:

#include<sys/tyoes.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

sockfd参数是执行过listen系统调用的监听socket;addr参数用来获取被接受连接的远端socket地址,该socket地址的长度由addrlen参数指出。accept成功时返回一个新的链接socket。失败时返回-1并设置errno。

发起连接

客户端通过connect主动与服务器建立连接

#include<sys/types.h>
#include<>ys/socket.h>
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);

sockfd参数由socket系统调用返回一个socket。serv_addr参数是服务器监听的socket地址,addrlen参数则指定这个地址的长度。
connect成功时返回0,一旦成功建立连接,sockfd就唯一标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。失败则返回-1并设置errno。
常见错误是ECONREFUSED,表示目标端口不存在连接被拒绝;ETIMEOUT表示连接超时。

关闭连接

关闭一个连接就是关闭该连接对应的socket。

#include<unist.h>
int close(int fd);

fd参数是待关闭的socket。close系统调用并非总是立即关闭一个链接,而是将fd的引用计数减一。只有当fd的引用计数为时,才真正关闭连接。
如果要立即终止连接,可以使用shutdown系统调用

#include<sys/socket.h>
int shutdown(int sockfd,int howto);

sockfd参数是待关闭的socket,howto参数决定了shutdown的行为。下面是可选值

SHUT_RD关闭sockfd上读的这一半。应用程序不能再针对socket文件描述符执行读操作,并且该socket接收缓冲区中的数据都被丢弃
SHUT_WR关闭sockfd上写的这一半。sockfd的发送缓冲区中的数据会在真正关闭连接之前全部发送出去。应用程序不可再对该socket文件描述符执行写操作。这种情况下,连接处于半关闭状态
SHUT_RDWR同时关闭sockfd上的读和写

shutdown成功时返回0,失败则返回-1并设置errno。

TCP数据读写

#include<sys/types.h>
#include<sys/socket.h>
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读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小。recv成功时返回实际读到的数据的长度,它可能小于len。recv可能返回0,代表通信对方已经关闭连接。recv出错时返回-1并设置errno。
send往sockfd写入数据,buf和len参数分别指定写缓冲区的位置和大小。send成功时返回实际写入数据的长度,失败则返回-1并设置errno。
flags参数为数据收发提供了额外的控制,一般设置为0。

UDP数据读写

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen);
ssize_t sento(int sockfd,const void* buf,size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);

recvfrom读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小。因为UDP没有连接的概念,所以需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度。
sendto往sockfd写入数据,buf和len参数分别指定写缓冲区的位置和大小。dest_addr参数指定接收端的socket地址,addrlen参数指定地址的长度。
recvfrom和sendto这两个系统调用也可以用于面向连接的数据读写,只需包最后两个参数都设置为NULL。

通用数据读写函数

既能用于TCP,又能用于UDP

#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg,int flags);
ssize_t sendmsg(int sockfd,struct msghdr* msg, int flags);
struct msghdr
{
	void* msg_name;//socket地址
	socklen_t msg_namelen;//socket地址长度
	struct iovec* msg_iov; //分散的内存块
	int msg_iovlen;//分散内存块的数量
	void* msg_control;//指向辅助数据的起始位置
	socklen_t msg_controllen;//辅助数据的大小
	int msg_flags;//复制函数中的flags参数,并在调用过程中更新
}

msg_name成员指向一个socket地址结构变量,它指定通信对方的socket地址。对于面向连接的TCP协议,该成员没有意义,必须设置为NULL。msg_namelen成员则指定msg_name所指socket地址的长度。
msg_iov成员是iovec结构体类型的指针,定义如下

struct iovec
{
	void *iov_base;//内存起始地址
	size_t iov_len;//这块内存的长度
}

iovec结构体封装了一块内存的起始位置和长度。msg_iovlen指定这样的iovec结构对象有多少个。对于recvmsg而言,数据将被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和长度则有msg_iov指向的数组指定,这称为分散读;对于sendmsg而言,msg_iovlen块分散内存中的数据将被一并发送,这成为集中写。

带外数据

带外数据又称快读数据。有时候在一个网络连接的终端想快速告诉网络另一边的终端一些信息。这个“快速”的意思是我们的提示信息会在正常的网络数据(带内数据)之前到达网络另一边的终端。带外数据比带内数据有更高的优先级。
实际应用中无法预计带外数据何时到来。Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据需要接收。内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。但是,即使应用程序得到了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能准确接收带外数据。这就需要用到带外标记。

#include<sys/socket.h>
int sockatmark(int sockfd);

sockatmark判断sockfd是否处于带外标记,即下一个被读到的数据是否是带外数据。如果是,则返回1,此时使用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则返回0。

地址信息函数

#include<sys/socket.h>
int getsockname(int sockfd,struct sockaddr* address, socklen_t* address_len);
int getpeername(int sockfd,struct sockaddr* address, socklen_t* address_len);

getsockname获取sockfd对应的本端socket地址,并将其存储于address参数指定的内存中,该socket地址的长度则存储于address_len参数指向的变量中。如果实际socket地址的长度大于address所指内存区的大小,那么该socket地址将被截断。getsockname成功时返回0,失败时返回-1并设置errno。
getpeername获取sockfd对应的远端socket地址,其参数及返回值意义与getsockname相同。

gethostbyname和gethostbyaddr

gethostbyname函数根据主机名称获取主机的完整信息。gethostbyaddr函数根据IP地址获取主机的完整信息。gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器。

#include<netdb.h>
struct hostent* gethostbyname(const char* name);
struct hsotent* gethostbyaddr(const void* addr,size_t len, int type);
struct hostent
{
	char* h_name;//主机名
	char** h_aliases;//主机别名列表,可能有多个
	int h_addrtype;//地址类型
	int h_length;//地址长度
	char** h_addr_list;//按网络字节序列出的主机IP地址列表
}

name参数指定目标主机的主机名,addr参数指定目标主机的IP地址,len参数指定addr所指IP地址的长度,type参数指定addr所指IP地址的类型,其合法取值包括AF_INET(用于IPv4地址)和AF_INET6(用于IPv6地址)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值