UNIX网络编程卷1(第三版)基本TCP套接字编程

时间:2022-07-24
本文章向大家介绍UNIX网络编程卷1(第三版)基本TCP套接字编程,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、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和端口号