所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制
套接字API
指明端点地址
为允许协议族自由地选择其地址表示方式,套接字抽象为每种类型的地址定义了一个地址族。TCP/IP各协议使用一种单一的地址表示方式,其地址族用符号常量AF_INET表示。TCP/IP协议族表示为PF_INET
类属地址结构
一般化的格式结构(地址族,该族中的端点地址)
地址族字段包含一个常量,表示预定义的地址类型
端点地址字段包含有端点地址,它使用了地址族所指明的那种地址类型的标准表示方式。
端点地址字段
最一般的结构是sockaddr结构,包含一个占2字节的地址族标识符,还有一个占14字节的数组存储地址。TCP/IP中,这种结构只能用于覆盖,而且代码只能引用sa_family字段。
struct sockaddr{
u_char sa_len;//总长度
u_short sa_family;//地址类型
char sa_data[14];//地址值
}
使用套接字的每个协议族都精确定义了它的端点地址。下面来看TCP/IP。
每个TCP/IP端点地址由下列字段构成:
-
一个用来识别地址类型的2字节字段,必须包含AF_INET
-
一个2字节的端口号字段(port number)
-
一个4字节的IP地址字段
-
一个还未使用的8字节字段
struct sockaddr_in{ u_char sin_len; u_short sin_family;//地址类型 u_short sin_port;//protocol port number struct in_addr sin_addr;//IP address char sin_zero[8];//未使用,设为0 }
只使用TCP/IP协议的应用程序可以只使用sockaddr_in结构;他不需要使用sockaddr结构
套接字API中的主要系统调用
套接字调用分为两组:
- 主调用:提供对下层功能的访问
- 实用例程:为程序员提供帮助
-
socket
int socket(int domain,int type,int protocol); 返回值:文件描述符表示成功,-1表示错误,errno记录错误代码
domain: 套接字的地址族
type: 常用SOCK_STREAM和SOCK_DGRAM
protocol: 一般情况为0,代表由系统在当前设定的domain下,自动选择适合的协议类型
-
connect
int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen); 返回值:0表示成功。-1表示失败,errno记录错误代码
servaddr: 客户端准备连接的服务器地址
-
send
send(数据将要发往的套接字描述符,数据要发往的地址,数据的长度)
send往往要将外发数据复制到操作系统内核中的缓存里,可以一边发一遍复制。
如果缓存满了,send可能暂时阻塞,直到TCP可以通过网络发送数据并在缓存中为新数据腾出空间为止
-
recv
客户和服务器都使用recv从TCP接受数据。在建立好连接后,服务器往往用recv接受客户通过调用send而发送的请求。而客户在发送完成请求之后,使用recv接收应答。
recv(所使用的套接字描述符,缓存地址,缓存长度)
recv抽出已到达该套接字的数据字节,并将其复制到用户缓存中。
若没有数据到达,recv阻塞,直到有数据为止。
如果到达的数据比缓存的容量多,只抽出能满足缓存的数据。
如果到达的数据比缓存的容量少,抽出所有的数据并返回它所找到的字节数。
客户和服务器也可以使用recv接受来自套接字(使用UDP)的报文。
这时,对recv的每次调用将取出一个进来的UDP报文。若缓存不能装下整个报文,recv将填满缓存并把剩下的数据丢弃。
-
close
客户或服务器一旦结束使用某个套接字,便调用close将该套接字撤销。
只有一个进程使用的话,close立即终止连接并撤销该套接字。
多个进程共享某个套接字,close把套接字的引用数减一,当为0时,就撤销该套接字。
-
bind
套接字被创建后,它还没有任何关于端点地址的概念(本地地址和远程地址都没有指派)。应用程序调用bind以便为一个套接字指明本地端点地址。TCP/IP协议,端点地址使用sockaddr_in结构,包含了IP地址和协议端口号。服务器主要使用bind来指明熟知端口号,然后等待连接。
注意,服务器端需要绑定,而客户端不需要。因为通常都是客户端主动发起的连接请求,因此客户端套接字在发出连接请求后,由内核自动绑定到一个临时端口和地址上。而作为服务器,一般是工作在被动连接的方式下,所以必须通过显示的调用bind(),将监听套接字绑定到一个众所周知的端口上,以等待客户端的连接。
-
listen
面向连接的服务器调用listen将一个套接字设置为被动模式,并使其准备接受传入连接。
-
accept
对于TCP套接字,服务器端先socket,然后bind指明本地端口地址,然后listen将套接字置于被动模式,然后服务器调用accept以获取接下来的传入连接请求。
-
read和write
在Linux中, read,write可以分别代替recv,send
套接字API详细
TCP/IP protocol family denoted by PF_INET,and the addres family it use ,denoted by AF_INET
protocol families same as address families for now
sockaddr用来定义socket address which is used in bind(),connect(),recvfrom() and sendto();
struct in_addr sin_addr;//ip address
struct in_addr{
in_addr_t s_addr;
uint32_t in_addr_t;
}
socket()
int socket(int domain,int type,int protocol)
-
domain
Specifies the communications domain in which a socket is to be created
-
type
argument Specifies the type of socket to be created
-
protocol
Specifies a particular protocol to be used with the socket. Specifying a protocol of 0 causes socket() to use an unspecified default protocol appropriate for the requested socket type
-
返回值
successful completion: return non-negative integer,the socket file descriptor
example:
if((s=socket(PF_INET,SOCK_STREAM,0))<0){
err(1,"create socket");
}
connect()
int connect(int socket,
const struct sockaddr *address,
socklen_t address_len);
-
socket
specifies the file descriptor associated with the socket
-
address
its length and format depend on the address family of socket
-
address_len
length of address argument
-
return values
successful completion , return 0.
otherwise, -1 shall be returned
example:
int s;//socket file descriptor
struct sockaddr_in sin;
...
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_addr.s_addr=inet_addr("192.168.10.110");
sin.sin_port=htons(2012);
...
if(connect(s,(struct sockaddr *)&sin,sizeof(sin))<0){
err(1,"connect socket");
}
send()-在连接socket上发送信息
ssize_t send(int socket, const void *buffer,size_t length,int flags);
-
socket: specifies the socket file descriptor
-
buffer: buffer指针包含要发送的内容
-
length:the length of the message in bytes
-
flags: the type of message transmission
0或者msg_eor 或者msg_oob
-
return values
成功的话,返回发送的字节数
example:
int s;
char buf[128];
sprintf(buf,"hello");
if(send(s,buf,strlen(buf),0)<0){
err(1,"send socket");
}
note:strlen是5,而sizeof(buf)是128
sendto()
ssize_t sendto(int socket,
const void *message,size_t length,int flags,
const struct sockaddr *dest_addr,
socklen_t dest_len);
note:dest just is destination
example:
struct sockaddr_in sin;
sendto(s,buf,strlen(buf),0,(struct sockaddr *)&sin,sizeof(sin))
sendmsg()
ssize_t sendmsg(int socket,const struct msghdr *message,int flags);
-
message
指针message包含了目的地地址和外部信息的缓存
recv()-从连接socket接受信息
ssize_t recv(int socket,void *buffer,size_t length,int flags)
-
buffer
信息保存的地方
-
length
buffer 参数的长度
-
flags
specifies the type of message reception
-
return values
信息长度(in bytes):成功
0 :没信息
-1:error
前面已经connect();
recv(s,buf,sizeof(buf),0);
recvfrom()
ssize_t recvfrom(int socket,void *restrict buffer,size_t len,
int flags,struct sockaddr *restrict address,
socklen_t *restrict address_len
)
前面不需要connect了,因为connect(socket,address,len)里面的参数在recvfrom里面全有了。
example:
cjar fbuf[128];
int s,alen;
struct sockaddr_in sin,fsin;
s=socket(PF_INET);
alen=sizeof(fsin);
recvfrom(s,fbuf,sizeof(fbuf),0,(struct sockaddr*)&fsin,&alen);
recvmsg()
ssize_t recvmsg(int socket,struct msghdr *message,int flags);
-
message
包含了存储源地址的buffer和保存信息的buffers
close()
int close(int fildes);
0成功,-1失败;
bind()-bind a name to a socket
int bind(int socket,const struct sockaddr *address,socklen_t address_len);
0成功;
-
address
被绑定到socket的地址
example:
struct sockaddr_in sin;
int msock;
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_addr.s_addr=INADDR_ANY;
sin.sin_port=htons(2012);
msock=socket(PF_INET,SOCK_STREAM,0);
bind(msock,(struct sockaddr *)&sin,sizeof(sin));
listen()
面向连接才使用
int listen(int socket,int backlog)
-
socket
这个参数是由socket()创建,由bind()绑定了地址的
-
backlog
队列最大长度
-
成功返回0
accept()
int accept(int socket,struct sockaddr *restrict address,
socklen_t *restrict address_len)
-
socket
经历了socket(),bind()和listen()
-
address
要么空,要么是the address of the connecting socket shall be returned
-
返回值
成功,返回file descriptor of the accepted socket
example:
if((msock=socket(PF_INET,SOCK_STREAM,0))<0) error();
if(bind(msock,(struct sockaddr *)&sin,sizeof(sin))<0) err();
if(listen(msock,32)<0) err();
while(1){
alen=sizeof(fsin);
if((ssock=accept(msock,(struct sockaddr *)&fsin,&alen))<0){
err();
}
}
write and read
ssize_t read(int fildes,void *buf,size_t nbyte);
ssize_t write(int fildes,const void *buf,size_t nbyte);
等同于recv和send,没有了flag而已
只有recvfrom()和accept()里面的地址长度是指针类型的
htonl()和htons()
host->net
返回从host转换的network byte order
ntohl()和ntohs()
network->host
返回从network转换的host byte order
client和server的设计
inet_addr和inet_ntoa
ipv4 地址操作。
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
将一个点分十进制的IP转换成一个长整数型数;
将网络地址转换成“.”点隔的字符串格式;
getaddrinfo 和freeaddrinfo
getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构的链表而不是一个地址清单。
int getaddrinfo(const char *restrict hostname,
const char *restrict servname,
const struct addrinfo *restrict hints,
struct addrinfo **restrict res);
void freeaddrinfo(struct addrinfo *ai);
-
hostname and servname
要么空,要么指向以空结尾的字符串。
这两个实参中的一个或两个都应由应用程序作为非空指针提供。
-
hostname
一个主机名或者地址串
-
servname
服务名可以是十进制的端口,也可以是已定义的服务名称,如ftp,http
-
-
hints
可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:指定的服务既可支持TCP也可支持UDP,所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
struct addrinfo { int ai_flags; int ai_family;//ipv4或者ipv6;AF_UNSPEC代表接受任意地址族 int ai_socktype;//流或者数据报 int ai_protocol;//一般是零 socklen_t ai_addrlen; //ai_addr的地址长度 struct sockaddr_in *ai_addr; char *ai_canonname; struct addrinfo *ai_next; //指向链表的下一个结点 };
-
返回值
0成功;非零失败
example:
struct addrinfo hints;
struct addrinfo *res, *cur;
int ret;
struct sockaddr_in *addr;
char m_ipaddr[16];
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* Allow IPv4 */
hints.ai_flags = AI_PASSIVE;/* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_socktype = SOCK_STREAM;
ret = getaddrinfo("cplusplus_me", "http",&hints,&res);
if (ret == -1) {
perror("getaddrinfo");
exit(1);
}
for (cur = res; cur != NULL; cur = cur->ai_next) {
addr = (struct sockaddr_in *)cur->ai_addr;
sprintf(m_ipaddr, "%d.%d.%d.%d",
(*addr).sin_addr.S_un.S_un_b.s_b1,
(*addr).sin_addr.S_un.S_un_b.s_b2,
(*addr).sin_addr.S_un.S_un_b.s_b3,
(*addr).sin_addr.S_un.S_un_b.s_b4);
printf("%s\n",m_ipaddr);
}
freeaddrinfo(res);
return 0;
getnameinfo
将socket address变为node name and service location
int getnameinfo(const struct sockaddr *restrict sa,
socklen_t salen,
char *restrict node, socklen_t nodelen,
char *restrict service, socklen_t servicelen,
int flags);
-
sa
指向将要被转换的socket地址
-
node and nodelen
1.If the node argument is NULL or the nodelen argument is zero, the node name shall not be returned.
2.node argument is non-null and the nodelen is non-zero,
那么node 接受到node 的以空结尾的字符串名字
3.无法找到node’s name ,返回node address的数字形式
-
返回值
成功,node and service name