C/S架构的简单时间获取程序的实现
Github地址:https://github.com/wangjiwang/C-S-
C/S 结构,即大家熟知的客户机和服务器结构。
本程序完成一个简单的时间获取功能,服务器依次调用socket,bind,listen三个函数准备监听描述符。
服务器进程调用accept函数,在accept调用中被投入睡眠,等待某个客户的连接到达并被内核接受。
TCP客户调用connect函数来建立与TCP服务器的连接,连接后调用read函数来读取服务器的应答,并用fputs函数输出结果显示在屏幕上。
运行时先运行服务器server.c程序,再运行客户端client.c程序(注意应加上自己电脑的IP地址),将会在屏幕上获取时间,如下图所示。

(图1 服务器运行结果)

(图2 客户端运行结果)
源代码如下
服务器程序:
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXLINE 4096
#define LISTENQ 1024
int main(int argc, char ** argv)
{
int listenfd,connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
//创建TCP套接字
listenfd = socket(AF_INET,SOCK_STREAM,0);
//填写一个网际套接字地址结构并调用bind函数,服务器的端口我们设置为9500
//IP地址我们指定为127.0.0.1(这个要根据你自己的IP地址指定)
bzero(&servaddr ,sizeof(servaddr ));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(9500);//端口的设置
//bind函数把一个本地的协议地址赋予一个套接字。
//第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr ));
//调用listen函数把该套接字转换成监听套接字
//第二个参数是指定这个监听描述符上排队的最大客户连接数
listen(listenfd,LISTENQ);
//调用accept函数
//第一个参数叫监听套接字描述符,由socket创建,随后作用与bind和listen的第一个参数描述符
//accept函数成功返回时,返回一个已连接套接字描述符
//注意!监听套接字描述符与已连接套接字描述符是不一样的
//一个服务器通常仅仅创建一个监听套接字,它在该服务的生命周期内一直存在。
//内核会为每个与服务器成功连接的客户创建一个新的套接字,即已连接套接字
//当服务器完成对某个客户的服务时,相应的连接套接字就被关闭掉
for(;;){
connfd = accept(listenfd ,(struct sockaddr*)NULL,NULL);
ticks = time (NULL);
snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));
write(connfd,buff,strlen(buff));
close(connfd);
}
return 0;
}
客户端程序:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#define MAXLINE 4096
int main(int argc,char **argv)
{
int sockfd,n;
char recvline[MAXLINE+1];
struct sockaddr_in servaddr;
if (argc != 2)
{
perror("usage:a.out<IPaddress>");
}
//socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM )套接字
//该函数返回一个小整数描述符,接下来的所有函数调用就用描述符来标识该套接字
//如果调用函数函数失败,则返回-1,输出提示信息
if ( (sockfd = socket(AF_INET,SOCK_STREAM,0) )<0)
{
perror("socket error");
}
//把服务器的IP地址和端口号填入一个网际套接字地址结构(一个名为servaddr的sockaddr_in结构变量)
//使用bzero函数清零结构体,把地址族设为AF_INET,端口号设置为9500
//IP地址为第一个命令行参数的值argv[1]
//因为网际套接字地址结构的IP地址和端口号必须使用特定格式
//所以调用htons函数(主机到网络短整数函数)去转换二进制端口号
//再调用inet_pton 把命令行参数IP地址转换为合适的格式
bzero (&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9500);//连接服务器端口
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<=0)
perror ("inet_pton error");
//调用connect函数建立与服务器的TCP连接,所连接的服务器由该函数的第二个参数指向的套接字地址结构指定
//该函数的第三个参数指定套接字地址结构的长度,我们用sizeof操作符来获取
if (connect (sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))>0) {
perror ("connect error ");
}
//使用read函数来读取服务器的应答,并用fputs函数输出结果
//如果数据量大,read函数不能返回服务器的整个应答
//因此,我们总是把read编写在某个循环中,当返回0(表明传输完成)和返回-1(表明发生错误)时循环结束
while ((n = read(sockfd,recvline,MAXLINE))>0) {
recvline[n]=0;
if (fputs(recvline,stdout)==EOF) {
perror ("fputs error");
}
}
if (n < 0) {
perror ("read error");
}
printf("服务端ip是127.0.0.1\n");
printf("服务端监听端口是9500\n");
printf("服务器发送的文件为wjw.txt\n");
//exit终止程序运行,关闭该进程所有打开的描述符
exit(0);
}
改程序编译及测试环境为CentOS6.8。
参考书籍:《UNIX网络编程 卷1:套接字联网API》(第三版 W.Richard Stevens Bill Fenner Andrew M.Rudoff)

浙公网安备 33010602011771号