一、socket函数
#include <sys/socket.h>
int socket(int family,int type ,int protocol);
//返回:若成功则为非负描述符,若出错则为-1
其中family参数指明协议族,type参数指明套接字类型,proctocol参数为协议类型或者0
并非所有的套接字famliy和type都有效。
family | 说明 |
AF_INET | IPv4协议 |
AF_INET6 | IPv6协议 |
AF_LOCAL | Unix域协议 |
AF_ROUTE | 路由套接字 |
AF_KEY | 密匙套接字 |
socket函数的type常值
type | 说明 |
SOCK_STREM | 字节流套接字 |
SOCK_DGRAM | 数据报套接字 |
SOCK_SEQPACKET | 有序分组套接字 |
SOCK_RAW | 原始套接字 |
socket函数的protocol常数值
protocol | 说明 |
IPPROTO_TCP | TCP传输协议 |
IPPROTO_UDP | UDP传输协议 |
IPPROTO_SCTP | SCTP传输协议 |
二、connect函数
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen);
//返回:若成功则为0,若出错则为-1
sockfd是由socket函数返回的套接字描述符
第二个参数:一个指向套接字地址结构的指针
第三个参数:该结构体的大小
套接字的地址结构必须含有服务器IP地址和端口号
三、bind函数
bind函数把一个本地协议地址赋予一个套接字。
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
//返回:若成功则为0,若出错则为-1
对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。
四、listen函数
listen函数仅由TCP服务器调用,它做两件事情:
1.当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受向该套接字的连接请求。调用listen函数导致套接字从closed状态转换到listen状态。
2.本函数的第二个参数规定了内核应该为相应的套接字排队的最大连接数
#include <sys/socket.h>
int listen(int sockfd,int backlog);
//返回:若成功则为0,若出错则为-1
本函数通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。
为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:
(1)未完成连接队列,每个这样的SYN分节对应其中一项,已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_RCVD状态
(2)已完成连接队列。每个已完成的TCP三次握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态。
五、accept函数
accept函数由服务器调用用于已完成的连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
//返回:若成功则为非负描述符,若出错则为-1
六、fork和exec函数
#include <unistd.h>
pid_t fork(void)
fork两个典型用法
1.一个进程创建一个自身副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。这是典型的网络服务器的用法。
2.一个进程想要执行另一个程序,既然创新进程的位置方法是调用fork,该进程于是首先调用fork创建一个自身的副本,然后其中一个副本(通常是子程序)调用exec把自身替换成新程序,这是诸如shell之类程序的典型用法。
#include <unistd.h>
int execl(const char *pathname,const char *arg0,..)
int execv(const char *pathname,char *const *argv[]);
int execle(const char *pathname,const char *arg0,...);
int execve(const char *pathname,char *const argv[],char *const envp[]);
七、并发服务器
unix中编写并发服务器程序的最简单的办法就是fork一个子进程来服务每个客户,
int
main(int argc, char **argv)
{
pid_t pid;
int listenfd, connfd;
socklen_t len;
struct sockaddr_in servaddr;
time_t ticks;
//创建套接字
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
//初始化套接字
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;//IPv4协议
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//通配地址,一般为0
servaddr.sin_port = htons(13);//时间服务端口
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = Accept(listenfd, (SA *) &cliaddr, &len);
if((pid = fork())==0)
{
close(listenfd);
doit(connfd);
close(connfd);
exit();
}
Close(connfd);
}
}
分析以上程序:
父进程:pid为子进程ID,不为0,则将connfd的引用套接字减1,父进程继续等待下一个客户连接
子进程:fork函数之后,监听套接字和已连接套接字的引用技术都加1,pid==0,首先监听套接字listenfd的引用计数减1(不会关闭监听套接字),然后执行客户所需的操作(doit),再关闭connfd(引用计数减1,此时为0)。子进程处理客户需求结束,exit关闭进程。
八、close
用来关闭套接字,并中止TCP连接。
#include <unistd.h>
int close(int sockfd);/* 若成功则返回0,出错则返回-1*/
close函数调用后只是将引用计数减1,只有当引用技术为0时,才会测地关闭该套接字,清理和资源释放。
九、getsockname和getpreername
getsockname函数返回与某个套接字关联的本地协议地址,getpeername函数返回与某个套接字关联的外地协议地址。
#include <sys/socket.h>
int getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);
int getpeername(int sockfd,struct sockaddr *peeraddr,socklen_t *addrlen);
需要使用上述函数的情况如下:
(1) 在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回由内核赋予该连接的本地IP地址和本地端口号
(2) 在以端口0调用bind后,getsockname用于返回由内核赋予的本地端口号
(3) getsockname用于获取某个套接字的地址族
(4) 以通配IP地址调用bind的服务器上,与客户一旦建立连接,getsockname可用于返回由内核赋予该连接的本地IP地址
(5) 在一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它只能通过getpeername来获取客户的IP和端口号