int socket(int family, int type, int protocol);
/**
第一个参数为协议族:AF_INET为IPv4协议;
AF_LOCAL/AF_UNIX为Unix域、AF_ROUTE为路由、AF_INET6为IPv6协议;
第二个参数为套接字类型:SOCK_STREAM字节流、SOCK_DGRAM数据报;
第三个参数为传输协议:IPPROTO_TCP为tcp、IPPROTO_UDP为udp、IPPROTO_SCTP为sctp;
*/
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
/**
返回套接字描述符,第二、三个参数是套接字地址结构指针(包含服务器IP和端口)和长度;
之前不是必须要调用bind,内核会确定源IP地址并选择临时端口作为源端口;
若未收到SYN确认,重发时间间隔6s、24s、75s、返回错误;
若收到RST,三种情况:1)指定端口没有进程等待与之连接,硬错误;2)TCP想取消一个已有的连接;
3)TCP接收到一个根本不存在的连接上的分节;
返回错误可能是客户发出的SYN在某个路由引发目的地不可达ICMP错误,软错误
将激发三次握手过程,从closed -> SYN_SENT,若成功返回ESTABLISHED。
*/
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
/**
本地协议地址赋予一个套接字;第二、三个参数是协议地址结构指针(可以指定IP和端口,也可以不指定)和长度;
若没有绑定IP地址,内核将客户发送的SYN目的IP作为服务器源IP;
若指定端口为0,内核在bind被调用是选择一个临时端口(INADDR_ANY == 0);
*/
int listen(int sockfd, int backlog);
/**
listen仅为TCP服务器调用:1)把一个未连接的套接字转化为被动套接字,指示内核接收请求,CLOSED -> LISTEN;
2)规定内核应该为相应套接字排队的最大连接个数;
监听套接字两个队列:1)未完成连接队列,状态为SYN_RCVD,等待完成TCP三次握手;
2)已完成连接队列,状态为ESTABLISHED,已完成TCP三次握手;
backlog没有正式被定义过,不一定是两个队列总和的最大值,Berkeley给其增设一个模糊因子:backlog * 1.5;
不要把backlog设置为0;
当一个SYN到达,队列已满,将忽略;
理解SYN泛滥攻击;
*/
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
/**
accept发生在服务器端,若成功,返回值是一个由内核自动生成的全新描述符,
代表返回客户端描述符,区分第一个参数监听描述符;
最多返回三个值,其中还包括客户进程协议地址和该地址大小(可为NULL);
显示地址:例子
*/
#include <time.h>
#include <sys/socket.h>
/*server.cpp 获得服务器时间的简单迭代服务器例子*/
/*注意不能直接copy例子使用,观察套接字相关函数首字母都是大写的,有做相关处理 */
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t len;
struct sockaddr_in servaddr, cliaddr;
char buf[MAXLEN];
time_t ticks;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13);
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *)&cliaddr, &len);
printf("connection from %s port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
return 0;
}
#include <unistd.h>
pid_t fork(void);
/**
Unix派生新进程唯一方法;调用一次,返回两次;
子进程:返回0;父进程:返回子进程pid;
accept后fork子进程和父进程都得到已连接的套接字;(可close父进程的连接,子进程read/write)
用法:1)创建副本;2)一个进程执行另一个进程,fork后,子进程调用exec,可用于并发服务器
框架:
for ( ; ;) {
connfd = Accept(listenfd, ...);
if ( (pid = Fork()) == 0) { //子进程
Close(listenfd); //关闭监听
doit(connfd); //处理请求
Close(connfd); //关闭套接字连接
exit(0); //退出
}
Close(connfd); //父进程关闭子进程套接字
}
*/
int close(int sockfd);
/**
六个exec函数:execl、execv、execle、execve、execlp,ececvp;
1)execve是内核系统调用,其他五个都是调用execve的库函数;
关系图如下:
execlp execl execle
| | |
execvp -> execv -> execve
*/
描述符引用计数,并发服务器中,父进程关闭子进程套接字只会使引用计数减一,未达到零不会引发TCP四次握手;
父进程必须调用close(connfd),否则,会耗尽父进程套接字描述符(有限),另外,即使子进程调用close(connfd)描述符引用计数由2 - 1 = 1,未断开TCP连接。
#include <sys/socket.h>
/**
注意两个函数最后一个参数是值-结果参数
*/
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
/**
返回某个套接字关联的本地协议地址;
没有调用bind的客户,connect成功后,getsockname返回内核赋予该连接的本地IP地址和本地端口号;
获得某个套接字的地址族;
套接字描述参数必须是已连接的套接字描述符,不是监听套接字的描述符;
例子:
int sock_to_family(int sockfd)
{
struct sockaddr_storage ss;
socklen_t len;
len = sizeof(ss);
if (getsockname(sockfd, (SA *)&ss, &len) < 0) return -1;
return (ss.ss_family);
}
//sockaddr_storage最大的套接字地址空间,可以承载系统支持的任何套接字地址结构;
//getsockname该函数适合任何已打开的套接字描述符;
*/
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
/**
返回某个套接字关联的外地协议地址;
*/