SOCKET编程-时间服务器和客户端的实现

UNIX_NET

获取服务器时间

connect函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

DESCRIPTION
       The  connect()  system  call connects the socket referred to by the file descriptor sockfd to the address specified by addr.  The addrlen argument specifies the size of addr.  The format  of  the address  in  addr is determined by the address space of the socket sockfd; see socket(2) for fur‐
       ther details.

    If the socket sockfd is of type SOCK_DGRAM, then addr is the address to which datagrams are  sent by  default,  and  the  only address from which datagrams are received.  If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection  to  the  socket  that  is bound to the address specified by addr.

    Generally, connection-based protocol sockets may successfully connect() only once; connectionless protocol sockets may use connect() multiple times to change  their  association.   Connectionless sockets  may  dissolve  the  association by connecting to an address with the sa_family member of sockaddr set to AF_UNSPEC (supported on Linux since kernel 2.2).
    
RETURN VALUE
       If the connection or binding succeeds, zero is returned.  On error, -1 is returned, and errno  is set appropriately.

获取时间客户端程序实现

#include "unix_net_public.h"
#include <stdio.h>
#include <strings.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.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;

    //< 没有指定IP进行报错处理
    if (argc != 2)
    {
        UNIX_NET_DEBUG("usage: <IPaddress>\n");
        exit(1);
    }
    //< AF_INET 通常代表地址族协议  PF_INET 通产代表网络协议族协议
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) <  0)
    {
        perror("socket error!\n");
    }

    //<  将地址结构体中的数据进行清空处理
    bzero(&servaddr, sizeof(servaddr));
    
    //< 构造结构体
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8560); // 没有找到时间服务器使用ssh端口连接自己的电脑测试连 connect函数的实现


    //< 将点十进制地址转换为网络地址
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
    {
        perror("inet_pton error  \n");
    }

    //< 使用connect创建连接
    UNIX_NET_DEBUG("connect start\n");
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect error\n");
    }
    UNIX_NET_DEBUG("connect is end !\n");
    //< 接受服务器的回复数据
    while((n = read(sockfd, recvline, MAXLINE)) > 0)
    {
        recvline[n] = 0;
        if(fputs(recvline, stdout) == EOF)
        {
            perror("fputs error\n");
        }
    }

    if (n < 0)
    {
        perror("error exit()");
    }
    
    
    return 0;
}

支持IPv6的实现

#include "unix_net_public.h"


int
main(int argc, char **argv)
{
	int					sockfd, n;
	struct sockaddr_in6	servaddr;
	char				recvline[MAXLINE + 1];

	if (argc != 2)
		UNIX_NET_DEBUG("usage: a.out <IPaddress>");

    //< 使用网际协议  字节流的形式创建 socket句柄
	if ( (sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
		UNIX_NET_DEBUG("socket error");

	bzero(&servaddr, sizeof(servaddr));

	servaddr.sin6_family = AF_INET6;
	servaddr.sin6_port   = htons(8560);	/* daytime server */
    //< 将IP地址转换为网络地址
	if (inet_pton(AF_INET6, argv[1], &servaddr.sin6_addr) <= 0)
		UNIX_NET_DEBUG("inet_pton error for %s", argv[1]);

	if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
		UNIX_NET_DEBUG("connect error");

    //< 使用阻塞的凡是进行read函数读取数据
	while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
		recvline[n] = 0;	/* null terminate */
		if (fputs(recvline, stdout) == EOF)
			UNIX_NET_DEBUG("fputs error");
	}
	if (n < 0)
		UNIX_NET_DEBUG("read error");

	return 0;
}

获取时间服务器实现

#include "unix_net_public.h"


int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	struct sockaddr_in	servaddr;
	char				buff[MAXLINE];
	time_t				ticks;

    //< 创建套接字,文件描述符
	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
    //< 监听所有的笛子, Address to accept any incoming messages. 
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //< 尽量使用大于8000的端口,小端口在linux上使用可能会报错不让使用
	servaddr.sin_port        = htons(8560);	/* daytime server */

    //< 绑定套接字
	bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

	listen(listenfd, LISTENQ);

	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);
	}
}

以上代码全部在UBUNTU 16.04上测试通过,可以去github上下载对应的源码,直接编译

测试

$ ./putdaytimesrv &
[1] 2952
$ ./getdaytimetcpclient 127.0.0.1
[getdaytimetcpclient.c], lien  = [47]connect start
[getdaytimetcpclient.c], lien  = [52]connect is end !
Sun Jul 14 14:57:22 2019

数据发送的过程刚好是三次握手,四次挥手加上一次(PSH,ACK)

抓包分析:
启动wireshark 抓取lo网卡的网络数据:
然后执行上述操作,对操作过程中产生的网络数据进行抓包
整个过程经历了三次握手和四次挥手,途中总共有7帧数据,前三帧是三次握手,后四帧是四次挥手,中间的那一次是PSH, ACK帧,是数据的发送帧
在这里插入图片描述



小技巧

宏定义的使用&&变参函数的宏定义实现

#define UNIX_NET_DEBUG(...) do { \
                    printf("[%s], lien  = [%d]", __FILE__, __LINE__); \
                    printf(__VA_ARGS__); \
                     }while(0)

源码地址

github

关注公众号

在这里插入图片描述

发布了356 篇原创文章 · 获赞 134 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/andrewgithub/article/details/95887666