(一)getaddrinfo()的使用
【实验目的】
通过实例来熟悉getaddrinfo()的用法。
【实验代码】
/* getaddrinfo.c */
#include #include #include #include #include #include #include #include int main() { struct addrinfo hints, *res = NULL; int rc; memset(&hints, 0, sizeof(hints)); /*设置addrinfo结构体中各参数 */ hints.ai_flags = AI_CANONNAME; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; /*调用getaddinfo函数*/ rc = getaddrinfo("localhost", NULL, &hints, &res); if (rc != 0) { perror("getaddrinfo"); exit(1); } else { printf("Host name is %s\\n", res->ai_canonname); } exit(0); } (二)socket编程实验 【实验目的】 POSIX中两种线程同步机制,分别是互斥锁和信号量来实现各个线程的顺序执行。 【实验代码】 /*server.c*/ #include #include #include #include #include #include #include #include #define PORT 4321 #define BUFFER_SIZE 1024 #define MAX_QUE_CONN_NM 5 int main() { struct sockaddr_in server_sockaddr, client_sockaddr; int sin_size, recvbytes; int sockfd, client_fd; char buf[BUFFER_SIZE]; /*建立socket连接*/ if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1) { perror("socket"); exit(1); } printf("Socket id = %d\\n",sockfd); /*设置sockaddr_in 结构体中相关参数*/ server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); /*绑定函数bind*/ if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1) { perror("bind"); exit(1); } printf("Bind success!\\n"); /*调用listen函数*/ if (listen(sockfd, MAX_QUE_CONN_NM) == -1) { perror("listen"); exit(1); } printf("Listening....\\n"); /*调用accept函数,等待客户端的连接*/ if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size)) == -1) { perror("accept"); exit(1); } /*调用recv函数接收客户端的请求*/ memset(buf , 0, sizeof(buf)); if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1) { perror("recv"); exit(1); } printf("Received a message: %s\\n", buf); close(sockfd); exit(0); } /*client.c*/ #include #include #include #include #include #include #include #include #include #define PORT 4321 #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) { int sockfd, sendbytes; char buf[BUFFER_SIZE]; struct hostent *host; struct sockaddr_in serv_addr; if(argc < 3) { fprintf(stderr,"USAGE: ./client Hostname(or ip address) Text\\n"); exit(1); } /*地址解析函数*/ if ((host = gethostbyname(argv[1])) == NULL) { perror("gethostbyname"); exit(1); } memset(buf, 0, sizeof(buf)); sprintf(buf, "%s", argv[2]); /*创建socket*/ if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); exit(1); } /*设置sockaddr_in 结构体中相关参数*/ serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); serv_addr.sin_addr = *((struct in_addr *)host->h_addr); bzero(&(serv_addr.sin_zero), 8); /*调用connect函数主动发起对服务器端的连接*/ if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr))== -1) { perror("connect"); exit(1); } /*发送消息给服务器端*/ if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1) { perror("send"); exit(1); } close(sockfd); exit(0); } 【实验结果】 编译并运行程序,查看实验结果。 先启动服务器端,再启动客户端。注意服务器端和客户端可以分别是宿主机和目标机,只要配置好IP地址,既可以确保通信。 (三)fctnl()多路复用 【实验目的】 使用fcntl()来解决多路复用。 【实验代码】 /* net_fcntl.c */ #include #include #include #include #include #include #include #include #include #include #include #include #define PORT 1234 #define MAX_QUE_CONN_NM 5 #define BUFFER_SIZE 1024 int main() { struct sockaddr_in server_sockaddr, client_sockaddr; int sin_size, recvbytes, flags; int sockfd, client_fd; char buf[BUFFER_SIZE]; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } server_sockaddr.sin_family=AF_INET; server_sockaddr.sin_port=htons(PORT); server_sockaddr.sin_addr.s_addr=INADDR_ANY; bzero(&(server_sockaddr.sin_zero),8); int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } if(listen(sockfd,MAX_QUE_CONN_NM)== -1) { perror("listen"); exit(1); } printf("Listening....\\n"); /* 调用fcntl函数设置非阻塞属性 */ flags = fcntl(sockfd, F_GETFL); if (flags < 0 || fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) < 0) { perror("fcntl"); exit(1); } while(1) { sin_size = sizeof(struct sockaddr_in); if ((client_fd = accept(sockfd, (struct sockaddr*)&client_sockaddr, &sin_size)) < 0) { perror("accept"); exit(1); } if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) < 0) { perror("recv"); exit(1); } printf("Received a message: %s\\n", buf); } /*while*/ close(client_fd); exit(1); } 【实验结果】 编译并运行程序,查看实验结果。 当accept()的资源不可用时,程序就会自动返回。 (四)select()多路复用 【实验目的】 服务器端代码使用select()函数来实现,客户端程序与实验(二)相同,仅仅加入一行sleep()函数,使得客户端进程等待几秒再结束。 【实验代码】 /* net_select.c */ #include #include #include #include #include #include #include #include #include #define PORT 4321 #define MAX_QUE_CONN_NM 5 #define MAX_SOCK_FD FD_SETSIZE #define BUFFER_SIZE 1024 int main() { struct sockaddr_in server_sockaddr, client_sockaddr; int sin_size, count; fd_set inset, tmp_inset; int sockfd, client_fd, fd; char buf[BUFFER_SIZE]; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } if(listen(sockfd, MAX_QUE_CONN_NM) == -1) { perror("listen"); exit(1); } printf("listening....\\n"); /*将调用socket函数的描述符作为文件描述符*/ FD_ZERO(&inset); FD_SET(sockfd, &inset); while(1) { tmp_inset = inset; sin_size=sizeof(struct sockaddr_in); memset(buf, 0, sizeof(buf)); /*调用select函数*/ if (!(select(MAX_SOCK_FD, &tmp_inset, NULL, NULL, NULL) > 0)) { perror("select"); close(sockfd); exit(1); } for (fd = 0; fd < MAX_SOCK_FD; fd++) { if (FD_ISSET(fd, &tmp_inset) > 0) { if (fd == sockfd) { /* 服务端接收客户端的连接请求 */ if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size))== -1) { perror("accept"); exit(1); } FD_SET(client_fd, &inset); printf("New connection from %d(socket)\\n", client_fd); } else /* 处理从客户端发来的消息 */ { if ((count = recv(fd, buf, BUFFER_SIZE, 0)) > 0) { printf("Received a message from %d: %s\\n", fd, buf); } else { close(fd); FD_CLR(fd, &inset); printf("Client %d(socket) has left\\n", fd); } } } /* end of if FD_ISSET*/ } /* end of for fd*/ } /* end if while while*/ close(sockfd); exit(0); } 【实验结果】 先启动服务器端,再反复运行客户端程序,观察运行结果。 (五)NTP协议实现 【实验目的】 通过实现NTP协议的练习,进一步掌握Linux网络编程,并且提高协议的分析与实现能力,为参与完成综合性项目打下良好的基础。 【实验代码】 /* ntp.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define NTP_PORT 123 #define TIME_PORT 37 #define NTP_SERVER_IP "210.72.145.44" #define NTP_PORT_STR "123" #define NTPV1 "NTP/V1" #define NTPV2 "NTP/V2" #define NTPV3 "NTP/V3" #define NTPV4 "NTP/V4" #define TIME "TIME/UDP" #define NTP_PCK_LEN 48 #define LI 0 #define VN 3 #define MODE 3 #define STRATUM 0 #define POLL 4 #define PREC -6 #define JAN_1970 0x83aa7e80 /* 从1900年到1970年之间的时间秒数 */ #define NTPFRAC(x) (4294 * (x) + ((1981 * (x)) >> 11)) #define USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16)) typedef struct _ntp_time { unsigned int coarse; unsigned int fine; } ntp_time; struct ntp_packet { unsigned char leap_ver_mode; unsigned char startum; char poll; char precision; int root_delay; int root_dispersion; int reference_identifier; ntp_time reference_timestamp; ntp_time originage_timestamp; ntp_time receive_timestamp; ntp_time transmit_timestamp; }; char protocol[32]; /*构建NTP协议包*/ int construct_packet(char *packet) { char version = 1; long tmp_wrd; int port; time_t timer; strcpy(protocol, NTPV3); /*判断协议版本*/ if(!strcmp(protocol, NTPV1)||!strcmp(protocol, NTPV2)||!strcmp(protocol, NTPV3)||!strcmp(protocol, NTPV4)) { memset(packet, 0, NTP_PCK_LEN); port = NTP_PORT; /*设置16字节的包头*/ version = protocol[6] - 0x30; tmp_wrd = htonl((LI << 30)|(version << 27)|(MODE << 24)|(STRATUM << 16)|(POLL << 8)|(PREC & 0xff)); memcpy(packet, &tmp_wrd, sizeof(tmp_wrd)); /*设置Root Delay、Root Dispersion和Reference Indentifier */ tmp_wrd = htonl(1<<16); memcpy(&packet[4], &tmp_wrd, sizeof(tmp_wrd)); memcpy(&packet[8], &tmp_wrd, sizeof(tmp_wrd)); /*设置Timestamp部分*/ time(&timer); tmp_wrd = htonl(JAN_1970 + (long)timer); memcpy(&packet[40], &tmp_wrd, sizeof(tmp_wrd)); /*Transmit Timestamp coarse*/ tmp_wrd = htonl((long)NTPFRAC(timer)); memcpy(&packet[44], &tmp_wrd, sizeof(tmp_wrd)); /*Transmit Timestamp fine*/ return NTP_PCK_LEN; } else if (!strcmp(protocol, TIME))/* "TIME/UDP" */ { port = TIME_PORT; memset(packet, 0, 4); return 4; } return 0; } /*获取NTP时间*/ int get_ntp_time(int sk, struct addrinfo *addr, struct ntp_packet *ret_time) { fd_set pending_data; struct timeval block_time; char data[NTP_PCK_LEN * 8]; int packet_len, data_len = addr->ai_addrlen, count=0, result, i, re; if (!(packet_len = construct_packet(data))) { return 0; } /*客户端给服务器端发送NTP协议数据包*/ if ((result = sendto(sk, data, packet_len, 0, addr->ai_addr, data_len)) < 0) { perror("sendto"); return 0; } /*调用select函数,并设定超时时间为1s*/ FD_ZERO(&pending_data); FD_SET(sk, &pending_data); block_time.tv_sec=10; block_time.tv_usec=0; if (select(sk + 1, &pending_data, NULL, NULL, &block_time) > 0) { /*接收服务器端的信息*/ if ((count = recvfrom(sk, data, NTP_PCK_LEN * 8, 0, addr->ai_addr, &data_len)) < 0) { perror("recvfrom"); return 0; } if (protocol == TIME) { memcpy(&ret_time->transmit_timestamp, data, 4); return 1; } else if (count < NTP_PCK_LEN) { return 0; } ret_time->leap_ver_mode = ntohl(data[0]); ret_time->startum = ntohl(data[1]); ret_time->poll = ntohl(data[2]); ret_time->precision = ntohl(data[3]); ret_time->root_delay = ntohl(*(int*)&(data[4])); ret_time->root_dispersion = ntohl(*(int*)&(data[8])); ret_time->reference_identifier = ntohl(*(int*)&(data[12])); ret_time->reference_timestamp.coarse = ntohl(*(int*)&(data[16])); ret_time->reference_timestamp.fine = ntohl(*(int*)&(data[20])); ret_time->originage_timestamp.coarse = ntohl(*(int*)&(data[24])); ret_time->originage_timestamp.fine = ntohl(*(int*)&(data[28])); ret_time->receive_timestamp.coarse = ntohl(*(int*)&(data[32])); ret_time->receive_timestamp.fine = ntohl(*(int*)&(data[36])); ret_time->transmit_timestamp.coarse = ntohl(*(int*)&(data[40])); ret_time->transmit_timestamp.fine = ntohl(*(int*)&(data[44])); return 1; } /* end of if select */ return 0; } int set_local_time(struct ntp_packet * pnew_time_packet) { struct timeval tv; tv.tv_sec = pnew_time_packet->transmit_timestamp.coarse - JAN_1970; tv.tv_usec = USEC(pnew_time_packet->transmit_timestamp.fine); return settimeofday(&tv, NULL); } int main() { int sockfd, rc; struct addrinfo hints, *res = NULL; struct ntp_packet new_time_packet; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; /*调用getaddrinfo函数,获取地址信息*/ rc = getaddrinfo(NTP_SERVER_IP, NTP_PORT_STR, &hints, &res); if (rc != 0) { perror("getaddrinfo"); return 1; } sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sockfd <0 ) { perror("socket"); return 1; } /*调用取得NTP时间函数*/ if (get_ntp_time(sockfd, res, &new_time_packet)) { /*调整本地时间*/ if (!set_local_time(&new_time_packet)) { printf("NTP client success!\\n"); } } close(sockfd); return 0; } 【实验结果】 先用date命令修改一下系统时间,再运行程序,运行之后查看系统时间,会发现已经恢复准确的系统时间了。