一、创建socket
1、int socket(int __domain, int __type, int __protocol)
作用:创建一个socket,
- __domain的value值,常用的几个值:(用于设置网络通信的域,根据这个参数选择通信协议族)
PF_UNIX,PF_LOCAL:本地通信
AF_INET,PF_INET:IPv4 internet协议
PF_INET6:IPv6 internet协议
- __type的value值,常用:(用于设置套接字通信的类型)
SOCK_STREAM:TCP连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输
SOCK_DGRAM:支持UDP连接(无连接状态的消息)
SOCK_SEQPACKET:序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出
__protocol:用于指定某个协议的特定类型,通常某个协议中只有一种特定类型,这样protocol参数仅能设置为0。
2、int setsockopt (int __fd, int __level, int __optname, const void *__optval, socklen_t __optlen)
一般在bind()之前使用
作用:用于给任意类型、任意状态socket接口设置选项值,选项由__optname指定。当level值为SOL_SOCKET时,代表在“套接字”层次上设置选项。选项影响socket接口的操作。
- __fd:获取选项的套接字
- __level:选项所在的协议层,目前来说只有两个层次SOL_SOCKET和IPPROTO_TCP
- __optname:需要访问的选项名
在socket层的一些选项名:
SO_DEBUG:打开调试信息模块
SO_BROADCAST:允许发送广播消息
SO_REUSEADDR:允许地址复用(有四个作用)
SO_KEEPALIVE:保持连接活跃。
- __optval:给optname指定的选项置初值,一般是bool或者int类型。
- __optlen:optval类型的长度
二、地址转换
int inet_pton(int af, const char *src, void *dst);
功能:将文本类型的IPv4和IPv6的地址转换成二进制类型
头文件:<arpa/inet.h>
第一个参数af只能是AF_INET或者是AF_INET6.
当af为AF_INET时,src指向的字符串的格式一般为"ddd.ddd.ddd.ddd",ddd可以取0到255之间的值。src指向的地址将会被转换为struct in_addr类型,然后复制到dst指向的地方;
当af为AF_INET6时,src指向的字符串的格式最好为x:x:x:x:x:x:x:x
(8个十六进制的数,每个数都表示一个16bit的值),除此之外还能将IPv4的地址转换为IPv6的地址表示,格式为x:x:x:x:x:x:d.d.d.d
(最后两个用IPv4的地址值代替)
这里官方有一个例子
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
unsigned char buf[sizeof(struct in6_addr)];
int domain, s;
char str[INET6_ADDRSTRLEN];
if(argc != 3){
fprintf(stderr, "Usage: %s {i4|i6<num>} string\n",argv[0]);
exit(EXIT_FAILURE);
}
domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
(strcmp(argv[1], "i6") == 0) ? AF_INET6 : atoi(argv[1]);
s = inet_pton(domain, argv[2], buf);
if (s <= 0) {
if (s == 0)
fprintf(stderr, "Not in presentation format");
else
perror("inet_pton");
exit(EXIT_FAILURE);
}
if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL) {
perror("inet_ntop");
exit(EXIT_FAILURE);
}
printf("%s\n", str);
exit(EXIT_SUCCESS);
}
2、uint32_t htonl(uint32_t hostlong);
功能:将host地址转换成网络字节序;
头文件:<arpa/inet.h> 在一些其他系统中可能是<netinet/in.h>
要注意的是传入参数是无符号整形,即IP为255.255.255.255,要传入的就是0xffffffff。
3、socket通信中使用的地址的结构(sockaddr和sockaddr_in)
在sys/socket.h中
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
在netinet/in.h中
struct sockaddr_in{
sa_family_t sin_family; //地址族
uint16_t sin_port; //16位TCP/UDP端口号
struct in_addr sin_addr; //32位IPv4地址
char sin_zero[8];
}
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr //32位IPv4地址
}
这个两个结构体占用的内存是一致的,因此可以相互转化。
sockaddr多用于bind、connect、recvfrom、sendto等函数的参数
而sockaddr_in一般是存储地址和端口的,用与信息的显示即存储使用。
三、bind
在绑定地址的时候,有时候将sockaddr_in
类型的变量中sin_addr
(in_addr类型)设置为INADDR_ANY,然后再绑定到socket上,在多网卡的情况下,绑定INADDR_ANY的话,就是可以用一个socket去监听所有网卡中指定的端口。
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t);
所需头文件:
<sys/types.h> //支持socklen_t类型
<sys/socket.h>
当一个socket被创建是,这个socket是在一个命名空间中的(类似于IPv4或者IPv6),但是没有指定一个地址给这个socket。
bind函数的作用就是将addr参数指向的地址分配给sockfd指向的socket
这里又有一个例子:
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MY_SOCK_PATH "/somepath"
#define LISTEN_BACKLOG 50
#define handle_error(msg) \
do {perror(msg);exit(EXIT_FAILURE);} while(0)
int main(int argc char *argv[]){
int sfd, cfd;
struct sockaddr_un myaddr, peer_addr;
socklen_t peer_addr_size;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd == -1)
handle_error("socket");
memset(&my_addr,0,sizeof(struct sockaddr_un));
my_addr.sun_family = AF_UNIX;
strncpy(my_addr.sun_path, MY_SOCK_PATH,sizeof(struct sockaddr_un) - 1);
if(bind(sfd,(struct sokaddr *)&my_addr,sizeof(struct sockaddr_un))==-1){
handle_error("bind");
}
if(listen(sfd,LISTEN_BACKLOG) == -1)
handle_error("listen");
peer_addr_size = sizeof(struct sockaddr_un);
cfd = accept(sfd, (struct sockaddr *)&peer_addr, &peer_addr_size);
if(cfd == -1)
handle_error("accept");
/* Code to deal with incoming connection */
}
四、listen
函数原型:
int listen(int sockfd, int backlog);
头文件:<sys/types.h> <sys/socket.h>
功能:主要是的作用就是将第一个参数的socket变为被动连接的套接字,从而成为一个服务器端的套接字。
backlog参数定义挂起连接队列的最大长度。当队列是满的时,对于发来的连接请求,客户端会受到一个error
在服务器端,tcp连接达到ESTABLISHED状态之前会经历中间状态SYN_RECEIVED,这意味着在调用listen之后,会存在两种状态的连接一种是SYN_RECEIVED,另外一种就是ESTABLISHED;这两种套接字可能再一个队列中(两种状态混在一起),也可能在两个队列中(两种状态分开保存)
1、使用单个队列实现:队列大小就backlog参数指定。当收到syn数据报之后就把socket加入队列,然后当收到相应的ack之后,将对应的连接状态变为established。处于后一状态的连接可以通过accept调用返回给客户端。
2、使用两个队列实现,一个syn队列(半连接队列)和一个accept队列(完整的连接队列)。处于SYN_RECEIVED状态的连接被添加到SYN队列中,并且当它们收到ack,变为established状态之后,将它们移动到accept队列中。accept调用就是从这个全连接队列中取出连接。在这种情况下,listen的backlog参数表示全连接队列的大小。
补:
在第一种实现方式中(一般是BSD),当队列满了之后,系统就不会再响应syn分组(不会再回复syn/ack),通常就是简单的丢弃syn分组,使客户端重试。
在第二种实现方式中(一般是Linux),半连接队列的大小是有内核设置的用 /proc/sys/net/ipv4/tcp_max_syn_backlog设置,默认值的256
五、accept
函数原型:
int accept(int sockfd, struct sockaddr *addr, sockl;en_t *addrlen);
头文件:<sys/types.h> <sys/socket.h>
功能:accept()系统调用是跟基于连接的SOCK_STREAM或者SOCK_SEQACKET类型的套接字一起使用的。
它的作用是在监听套接字的挂起连接队列中提取第一个链接请求。