AI智能
改变未来

网络编程-基于TCP/UDP的客户端获取服务器时间例子

TCP传输控制协议
向用户进程提供可靠的全双工字节流(字节流:给每一个字节编序)

UDP用户数据报协议
是一种无连接的协议

获取时间服务的客户端
client

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <unistd.h>#include <fcntl.h>#include <arpa/inet.h>int main(int argc,char *argv[]){int sockfd;struct sockaddr_in servaddr;char buf[100];//存储读取的内容int bytes;//存储读取的字节数if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)//创建套接字{printf(\"socket error\\n\");return -1;}//结构体中成员变量初始化为0bzero(&servaddr,sizeof(servaddr));//初始化成员变量servaddr.sin_family = AF_INET;//IPv4servaddr.sin_addr.s_addr = inet_addr(\"192.168.1.12\");//转换为地址格式servaddr.sin_port = htons(8888);//主机序转换到网络序//连接服务器if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)//绑定服务器ip和端口到>套结字{perror(\"connect error\");return -2;}//存储读取的数据和读取的字节数bytes = read(sockfd,buf,100);if(bytes < 0){printf(\"Error ,read failed\\n\");close(sockfd);return -3;}//如果读取的字节数为0 ,就说明连接已经关闭了if(0 == bytes){printf(\"Server close connection\\n\");close(sockfd);return -4;}//打印读取的字节数和读取的内容printf(\"read bytes %d\\n\",bytes);printf(\"Time: %s\\n\",buf);//关闭套结字close(sockfd);return 0;}

运行结果

read bytes 25Time: Tue Jul 21 14:45:10 2020

server

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <unistd.h>#include <fcntl.h>#include <time.h>#define MAX_LISTEN_QUE 5int main(int argc,char *argv[]){int listenfd,sockfd,opt=1;//listenfd创建的普通套接字,sockfd通信套接字struct sockaddr_in server,client;char buf[200];socklen_t len;time_t timep;int ret;//创建套接字listenfd = socket(AF_INET,SOCK_STREAM,0);if(listenfd < 0){perror(\"Create socket fail\");return -1;}//设置套结字关联的选项(设置地址的重用)if((ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0){perror(\"Error ,set socket reuse addr failed\");return -1;}//清空结构体中的变量值bzero(&server, sizeof(server));//初始化结构体的变量server.sin_family = AF_INET;//IPv4server.sin_port   = htons(8888);//主机转换到网络server.sin_addr.s_addr  = htonl(INADDR_ANY);//同上(使用这个宏套接字可以绑定到所有的端>口)//获取结构体地址长度len = sizeof(struct sockaddr);//绑定服务器ip地址和端口到套接字if(bind(listenfd, (struct sockaddr *)&server, len)<0){perror(\"bind error.\");return -1;}//设置服务器的最大连接数listen(listenfd, MAX_LISTEN_QUE);//迭代(就是可以有多个客户端只是不能同时通信)while(1){//等待客户端请求,如果请求到来,返回一个新的socket//服务器和客户端利用新的socket来通信sockfd = accept(listenfd, (struct sockaddr *)&client, &len);if(sockfd < 0){perror(\"accept error.\");close(sockfd);return -1;}//显示系统的当前时间timep = time(NULL);//将数据按照一定的格式转换之后复制到buf//ctime返回一个表示当地时间的字符串snprintf(buf, sizeof(buf), \"%s\", ctime(&timep));//向套接字中写入buf存储的时间write(sockfd, buf, strlen(buf));//打印存储的字节数printf(\"Bytes:%lu\\n\", strlen(buf));//打印套接字的文件描述符printf(\"sockfd=%d\\n\", sockfd);//关闭套接字close(sockfd);}return 0;}

运行结果

Bytes:25sockfd=4

三次握手

  1. 服务器必须准备好接受外来连接
  2. 客户端调用connect来主动打开一个连接,此时客户端TCP将会发送一个SYN分节
  3. 服务器必须确认客户的SYN
  4. 客户必须确认服务器的SYN

注: 在启动客户端和服务器端的时候,要先启动服务器端之后再启动客户端,然后进行抓包。使用windows下的命令提示符一旦启动程序是不会自己退出的。需要我们强制退出

四次挥手

TCP编程模型

网络字节序
htons 把 unsigned short类型从主机序转换到网络序
htonl把unsigned long类型从主机序转换到网络序
ntohs把unsigned short类型从网络序转换到主机序
ntohl把unsigned long类型从网络序转换到主机序

套接字的地址结构

通用地址结构:

struct sockaddr{sa_family_t  sa_family;char    sa_data[14];}

(2)、不同的协议,IPv4/IPv6表示方法:

IPv4:struct   in_addr{in_addr_t  s_addr;};struct   sockaddr_in{sa_family_t  sin_family;int  port_t      sin_port;struct  in_addr  sin_addr;char  sin_zero[0];}
IPv6:struct   in6_addr{in_addr_t  s6_addr[16];};struct   sockaddr_in6{sa_family_t  sin6_family;int  port_t      sin6_port;uint32_t  sin6_flowinfo;struct  in_addr  sin6_addr;uint32_t  sin6_scope_id;}

IP地址转换的相关函数

inet_addr函数: 将一个点分十进制的IP转换成一个长整数型数
原函数:unsigned long inet_addr(const char* cp)
参数:cp,代表点分十进制的IP地址,如1.2.3.4

int inet_aton函数: 将abcd形式的IP转换为32位的IP,存储在inp指针里面
原函数:int inet_aton(const char * cp,struct in_addr * inp)
参数:cp,包含ASCII表示的IP地址
   inp,将要用新的IP地址更新的结构

inet_ntoa函数: 将32位IP转换为ABCD的格式
原函数:char * inet_ntoa(struct in_addr in)
参数:in,代码in_addr的结构体

inet_pton函数: IP地址转换函数,可以在将IP地址在“点分十进制”转换为“二进制整数”,而且inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。
原函数:int inet_pton(int af, const char *src, void *dst)
参数:af,地址簇
   src,来源地址
   dst,接收转换后的数据

inet_ntop函数: 是一个IP地址转换函数,可以在将IP地址在“二进制整数”转换为“点分十进制”
原函数:const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
参数:af,地址簇
   src,来源地址
   dst,接收转换后的数据
   cnt,缓存区dst的大小

UDP编程模型

  1. 创建一个基于IPv4(AF_INET)的数据报套接字(SOCK_DGRAM)

  2. recvfrom函数: 从指定地址接收UDP数据报
    原函数:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
    struct sockaddr *src_addr, socklen_t *addrlen);
    参数:sockfd,socket的描述符
       buf,UDP数据报缓存
       len,UDP数据报长度
       flags,该参数一般为0
       src_addr,发送端的地址
       addrlen,发送端的地址长度
    返回值:成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。

  3. sendto函数: 把UDP数据报发给指定地址
    原函数:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
    const struct sockaddr *dest_addr, socklen_t addrlen);
    参数:sockfd,socket的描述符
       buf,UDP数据报缓存
       len,UDP数据报长度
       flags,该参数一般为0
       dest_addr,接收端的地址
       addrlen,接收端的地址长度
    返回值:成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。

server

#include<stdio.h>#include<string.h>#include<unistd.h>#include<sys/types.h>#include<sys/socket.h>#include<stdlib.h>#include<netinet/in.h>#include<arpa/inet.h>#include <time.h>#define PORT 8888#define MAXLEN 100int main(int argc,char *argv[]){int sockfd;struct sockaddr_in server;struct sockaddr_in client;char buf[100],read_buf[100];unsigned int len,length;time_t timep;int rv;bzero(read_buf,100);//创建套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){perror(\"Create socket failed.\");return -1;}//初始化结构体bzero(&server,sizeof(server));server.sin_family=AF_INET;//IPv4server.sin_port=htons(PORT);//端口号server.sin_addr.s_addr= htonl (INADDR_ANY);//设置为可以连接任何一个客户端len=sizeof(server);//将套接字和服务器的地址绑定if(bind(sockfd, (struct sockaddr *)&server, len) < 0){perror(\"Bind error.\");return -1;}while(1){//在recvfrom之前加,否则第一次读取发送方IP为0.0.0.0length = sizeof(struct sockaddr);//从客户端读取数据,并存储在read_buf中rv =recvfrom(sockfd,read_buf,MAXLEN,0,(struct sockaddr*)&client,&length);if (rv < 0){perror(\"recvfrom error\\n\");close(sockfd);return -1;}printf(\"len:%d\\n\",rv);printf(\"ip:%s,port:%d\\n\",inet_ntoa(client.sin_addr),ntohs(client.sin_port));printf(\"read_buf:%s\\n\",read_buf);timep = time(NULL);snprintf(buf ,sizeof(buf), \"%s\",ctime(&timep));//发送数据到客户端sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&client,length);bzero(&client,length);bzero(buf,MAXLEN);bzero(read_buf,MAXLEN );}//关闭套接字close(sockfd);return 0;}

运行结果

len:11IP:192.168.1.12,port:57759read_buf:Hello,UDP!

client

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netdb.h>#include<arpa/inet.h>#define PORT 8888#define MAXLEN 100int main(int argc, char *argv[]){int sockfd;struct sockaddr_in server,ser;char buf[100] = \"Hello,UDP!\\n\";char read_buf[100];unsigned int length,len;time_t timep;int rv;bzero(read_buf,100);sockfd=socket(AF_INET, SOCK_DGRAM,0);//创建套接字if (sockfd < 0){printf(\"Create socket error\\n\");return -1;}//初始化结构体bzero(&server,sizeof(server));server.sin_family = AF_INET;//IPv4server.sin_port = htons(PORT);//端口号inet_pton(AF_INET,\"192.168.1.12\",&server.sin_addr.s_addr);length = sizeof(server);//向服务器发送数据sendto(sockfd, buf,strlen(buf),0,(struct sockaddr *)&server,length);//在recvfrom之前加,否则第一次读取发送方IP为0.0.0.0len = sizeof(struct sockaddr_in);//从服务器读取数据,并存在read_buf中rv=recvfrom(sockfd,read_buf,MAXLEN,0,(struct sockaddr *)&ser,&len);if(rv < 0){printf(\"recvfrom error\\n\");close(sockfd);return -1;}printf(\"len:%d\\n\",rv);printf(\"IP:%s,Port:%d\\n\",inet_ntoa(ser.sin_addr),ntohs(ser.sin_port));printf(\"buf:%s\\n\",read_buf);bzero(&ser,len);bzero(buf,MAXLEN);bzero(read_buf,MAXLEN);close(sockfd);return 0;}

运行结果

len:25IP:192.168.1.12,Port:8888buf:Thu Jul 23 11:09:20 2020
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 网络编程-基于TCP/UDP的客户端获取服务器时间例子