一、Linux网络编程简介
Linux网络编程基于套接字。套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上运行,也可以跨网络进行。一台机器上的进程可以使用套接字和另外一台机器上的进程进行通信。同一台机器上的进程之间也可以使用套接字进行通信。Linux套接字严格区分客服端和服务端。
套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)
1.套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它是指Internet网络。其底层的协议网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即IP地址。套接字作为通信的终点,它必须在开始通信之前绑定一个端口。在计算机系统内部,端口通过分配一个唯一的16位的整数来表示,在系统外部,则需要通过IP地址和端口号的组合来确定。AF_UNIX也是常见的套接字类型,它主要用在Linux单机进程间的通信。
2.套接字类型
流套接字(在某些方面类似域标准的输入/输出流)提供的是一个有序,可靠,双向字节流的连接。流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。
数据包套接字
与流套接字相反,由类型SOCK_DGRAM指定的数据包套接字不建立和维持一个连接。它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。
3.套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。一般使用默认值0。
二、服务器/客户模型
三、Linux网络编程函数接口
3.1 创建套接字
socket系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字。
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
参数domain: AF_UNIX UNIX域协议(文件系统套接字);AF_INET ARPA因特网协议(UNIX网络套接字)
参数type: SOCK_STREAM是一个有序、可靠、面向连接的双字节流,基于TCP协议;SOCK_DGRAM是数据包服务,我们可以用它来发送最大长度固定的消息,基于UDP协议
参数protocol:参数指定使用的协议
返回值:返回一个套接字
3.2 命名套接字(服务器专用)
通过socket调用创建的套接字必须经过命名后服务器才能使用。bind系统调用把addr中的地址分配给与描述符socket关联的未命名套接字,地址结构的长度由addr_len指定。 addr和addr_len因地址族(AF_UNIX、AF_INET等)的不同而不同,bind调用时需要将指向特定地址结构的指针转化为指向通用地址的指针,即(struct sockaddr *)。
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *addr, size_t addr_len);
参数socket:由服务器创建的未命名的套接字描述符
参数addr:套接字相关联的地址
参数addr_len:地址的长度
返回值:成功返回0,失败返回-1
套接字地址结构
struct sockaddr_un
{
sa_family_t sun_family; //AF_UNIX
char sun_path; //pathname
};
struct sockaddr_in
{
short int sin_family; //地址类型,一般为AF_INET
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
};
struct in_addr
{
unsigned long int s_addr;
};
- 参数addr为通用地址结构,一般只需提供固定的端口号,即如下设置
- struct sockaddr_in server_add;
- server_add.sin_family = AF_INET;
- server_add.sin_addr.s_addr = htonl(INADDR_ANY); //接受任意IP地址的客户连接
- server_add.sin_port = htons(port); //port为服务器端指定的端口号,unsigned short int类型
- server_len = sizeof(server_add);
- bind(server_sockfd, (struct sockaddr*)&server_add, server_len);
3.3 创建套接字队列(TCP服务器专用)
服务器调用listen创建一个队列来保存未来得及处理的请求。
#include <sys/socket.h>
int listen(int socket, int backlog);
参数socket:服务器的监听套接字
参数backlog:套接字可以容纳的未处理链接的最大数目
返回值:成功返回0,失败返回-1
3.4 接受连接(TCP服务器专用)
服务器通过accept系统调用来等待客户端对该套接字的连接。accept只有当有客户程序试图连接到由socket参数指定的套接字时才会返回。accept将会创建一个新的套接字来与该客户进行通信,并且返回新套接字的描述符。
#include <sys/socket.h>
int accept(int socket, struct sockaddr *addr, size_t *addr_len);
参数socket:服务器命名过的套接字描述符;
参数addr:指向的socketadd地址结构用来存放将要连接到的客户的地址,只有accept成功返回时才有效。如果不关心客户地址,可以将addr参数指定为空指针
参数addr_len:指定客户地址结构的长度。如果超过这个值,客户地址将被截断。在accept调用之前,需要将其设置为预期的地址长度,当accept调用返回之后,addr_len将被设置为客户地址的实际长度
返回值:返回一个新的未命名套接字
- int client_sockfd;
- struct sockaddr_in client_add;
- clinet_len = sizeof(client_add);
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_add, &client_len);
注:accept中的addr_len为指向size_t类型的指针,bind中的addr_len为size_t类型。
如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)直到有客户建立连接请求。
可以通过对套接字描述符设置 O_NONBLOCK 标志来改变这一行为,使用的函数是 fcntl,如下:
- int flag = fcntl(socket, F_GETFL, 0);
- fcntl(socket, F_SETFL, O_NONBLOCK | flag);
当有未处理的请求时,accept返回一个新的套接字描述符。发生错误时,accept返回-1,并且设置errno的值。除了前面提到的之外,其他的错误有 EWOULDBLOCK 和 ENITR。前者是指定了O_NONBLOCK标志,但队列中没有未处理的请求;后者是当进程阻塞在accept调用时执行被中断。
3.5 请求连接(客户端)
客服程序通过一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器。它们通过connect调用来完成这一工作。
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *addr, size_t addr_len);
参数socket:客户端程序创建的未命名套接字描述符
参数addr:指向的为服务器端的地址结构
参数addr_len:地址的长度
返回值:成功时返回0,失败时返回-1并设置错误代码errno
- struct sockaddr_in server_add;
- server_add.sin_family = AF_INET;
- serevr_add.sin_addr.s_addr = inet_addr("***.***.***.***");//服务器地址,无需htonl转换,因为inet_addr已定义为网络字节序
- server_add.sin_addr.sin_port = htons(port); //port,int型变量,与服务器端相同的端口号
- len = sizeof(server_add);
- connect(sockfd, (struct sockaddr *)&server_add, len); //可根据返回值判断连接状态参数addr_len为addr指向的地址结构(即服务器地址)的长度。
3.6关闭套接字
int close(int sockfd)
四、套接字编程应用举例
4.1 简单的服务器/客户端模型
- 客户端代码如下:
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int sockfd;
- int len;
- struct sockaddr_un address;
- int result;
- char ch = 'A';
- sockfd = socket(AF_UNIX, SOCK_STREAM, 0);//新建单机通信的套接字
- address.sun_family = AF_UNIX;//初始化通信地址信息
- strcpy(address.sun_path, "server_socket");
- len = sizeof(address);
- result = connect(sockfd, (struct sockaddr *)&address, len);//连接到address指定的服务器
- if(result == -1) {
- perror("oops: client1");
- exit(1);
- }
- write(sockfd, &ch, 1);//阻塞发送一条数据给服务器,一般都不会阻塞
- read(sockfd, &ch, 1);//阻塞从服务器那里读取一条数据
- printf("char from server = %c\n", ch);
- close(sockfd);
- exit(0);
- }
- 服务器代码如下:
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <sys/un.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_un server_address;
- struct sockaddr_un client_address;
- unlink("server_socket");
- server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);//新建套接字
- server_address.sun_family = AF_UNIX;//初始化服务器套接字地址
- strcpy(server_address.sun_path, "server_socket");
- server_len = sizeof(server_address);
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);//命名套接字
- listen(server_sockfd, 5);//创建套接字等待队列
- while(1) {
- char ch;
- printf("server waiting\n");
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);//等待client_address中允许的用户连接
- read(client_sockfd, &ch, 1);//读取客户端数据
- ch++;
- write(client_sockfd, &ch, 1);//向客服端写数据
- close(client_sockfd);//关闭跟客户端的连接
- }
- }
4.2 fork实现处理多用户服务器
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <netinet/in.h>
- #include <signal.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_in server_address;
- struct sockaddr_in client_address;
- server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//新建套接字
- server_address.sin_family = AF_INET;//初始化套接字
- server_address.sin_addr.s_addr = htonl(INADDR_ANY);
- server_address.sin_port = htons(9734);
- server_len = sizeof(server_address);
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);//命名套接字
- listen(server_sockfd, 5);//创建套接字等待队列
- signal(SIGCHLD, SIG_IGN);//忽略子进程结束信号
- while(1) {
- char ch;
- printf("server waiting\n");
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);//等待客户连接请求
- if(fork() == 0) {//子进程执行下面代码
- read(client_sockfd, &ch, 1);
- sleep(5);
- ch++;
- write(client_sockfd, &ch, 1);
- close(client_sockfd);
- exit(0);
- }
- //父进程关闭和子进程的连接
- else {
- close(client_sockfd);
- }
- }
- }
4.3 select实现多用户处理服务器
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <netinet/in.h>
- #include <sys/time.h>
- #include <sys/ioctl.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_in server_address;
- struct sockaddr_in client_address;
- int result;
- fd_set readfds, testfds;
- server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
- server_address.sin_family = AF_INET;//初始化服务器地址
- server_address.sin_addr.s_addr = htonl(INADDR_ANY);
- server_address.sin_port = htons(9734);
- server_len = sizeof(server_address);
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);//命名套接字
- listen(server_sockfd, 5);//创建套接字等待队列
- FD_ZERO(&readfds);//清空readfds文件描述符集合
- FD_SET(server_sockfd, &readfds);//将server_sockfd放入的readfds中去
- while(1) {
- char ch;
- int fd;
- int nread;
- testfds = readfds;
- printf("server waiting\n");
- result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *) 0);//阻塞监听testfds中的文件
- if(result < 1) {
- perror("server5");
- exit(1);
- }
- for(fd = 0; fd < FD_SETSIZE; fd++) {//如果监听到文件可读,找出那个文件描述符
- if(FD_ISSET(fd,&testfds)) {
- if(fd == server_sockfd) {//如果是监听套接字可读,说明有新的客户连接请求
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);//接受新的客户请求
- FD_SET(client_sockfd, &readfds);
- printf("adding client on fd %d\n", client_sockfd);
- }
- else {//如果可读的文件描述符不是监听套接字,说明客户端有发送数据过来
- ioctl(fd, FIONREAD, &nread);//得到缓冲区可读的数据的个数,保存在nread中
- if(nread == 0) {
- close(fd);
- FD_CLR(fd, &readfds);
- printf("removing client on fd %d\n", fd);
- }
- else {
- read(fd, &ch, 1);//读取数据
- sleep(5);
- printf("serving client on fd %d\n", fd);
- ch++;
- write(fd, &ch, 1);
- }
- }
- }
- }
- }
- }
参看文献:linux程序设计第四版
文章出处:http://blog.csdn.net/qq_695538007/article/details/12581697