这个系列的文章主要是学习《UNIX 网络编程(卷一:套接字联网API)》的一些学习札记。
先看下这个简单的从服务器获取时间的客户端程序:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <error.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char **argv) { int sock_fd; struct sockaddr_in sock_addr; // 检查传入的参数是否正确 if(argc < 2) { printf("usage:time_client <IPAddress>"); return -1; } // 建立socket sock_fd = socket(AF_INET, SOCK_STREAM, 0); if(sock_fd < 0) { perror("socket:"); return -1; } // 设置需要连接的服务器的IP和端口号 bzero(&sock_addr, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_port = htons(3000); inet_pton(AF_INET, argv[1], &sock_addr.sin_addr); // 连接服务器 if(connect(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) { perror("connect:"); return -1; } // 从服务器端获取信息 char buff[1024]; int read_count; while( (read_count = read(sock_fd, buff, sizeof(buff))) > 0) { buff[read_count] = '\0'; printf("read string : %s\n"); } return 0; }
1、首先用socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字,该函数返回一个文件描述符,接下来的步骤都会用到这个返回的文件描述符。
2、连接服务器。这个首先要让程序知道服务器的地址和端口号,这就要用到一个struct sockaddr_in的网络套接字地址结构,因为有少数套接字函数要求网际套接字地址结构的最后8个字节置零(具体是哪几个函数,以后看到再补上),所以需要首先用bzero将struct sockaddr_in置零。在这里,ip地址和端口号不能按照常见的形式填入结构体里面,对于端口号,必须将其从主机序转换为网络序,这里要用到htons(host to net short)这个函数,ip地址则要将其从我们常见的写法转换成机器能识别的写法,并且将其转换为网络序,这里调用inet_pton来实现,该函数支持IPV6。设置完这些基本的信息后,就可以使用connect函数来连接服务器了。
3、读取时间信息。因为linux将所有的设备抽象成文件,所以,读取套接字的信息就可以像读取本地文件一样,使用read就可以了。
再接着看看服务器端的程序:
这个程序和客户端程序的差别主要是几个地方:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <error.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char **argv) { // 建立套接字 int server_fd; server_fd = socket(AF_INET, SOCK_STREAM, 0); if(server_fd < 0) { perror("socket:"); return -1; } // 设置套接字结构体 struct sockaddr_in sock_addr; bzero(&sock_addr, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_port = htons(3000); sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); // bind套接字 if( (bind(server_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr))) < 0) { perror("bind"); return -2; } // 将套接字转换为监听套接字 if (listen(server_fd, 1024) < 0) { perror("listen"); return -3; } // 接受连接请求,并发送服务器上的时间 int connect_fd; char buff[1024]; time_t current_time; while(1) { connect_fd = accept(server_fd, (struct sockaddr *)NULL, NULL); current_time = time(NULL); snprintf(buff, sizeof(buff), "%s \n", ctime(¤t_time)); write(connect_fd, buff, strlen(buff)); close(connect_fd); } return 0; }
1、bind。这个函数用于将服务器对外服务的端口绑定到套接字上,同时指定了IP地址为INADDR_ANY,通过设置该参数,可以设定服务器是从所有的网络接口上接收客户连接还是从指定的单个网络接口上接受客户端连接。
2、listen。这个函数把套接字转换成一个监听套接字,这样,来自客户的外来连接就可在该套接字上由内核接受。它的第二个参数用于指定系统内核允许在这个监听描述符上排队的最大客户连接数。
3、accept。服务器进程在调用这个函数后会进入睡眠状态,直到内核收到某个客户的连接请求。
4、写入时间信息。在接收客户端连接后,会得到一个这个连接的文件描述符,往这个描述符里面写入信息就可以了。
上面就是一个简单的时间服务的客户端和服务器的简单实现,如果其中有什么不对的地方,还请您斧正。