c++ 之 socket udp与tcp client server实现

服务器开发系列



前言

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用打开open –> 读写write/read –> 关闭close模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。


一、socket是什么?

socket(套接字)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合

socket 是一种IPC方法,它允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据。

套接字描述符
其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr
在这里插入图片描述
对于每个程序系统都有一张单独的表。精确地讲,系统为每个运行的进程维护一张单独的文件描述符表。当进程打开一个文件时,系统把一个指向此文件内部数据结构的指针写入文件描述符表,并把该表的索引值返回给调用者 。应用程序只需记住这个描述符,并在以后操作该文件时使用它。操作系统把该描述符作为索引访问进程描述符表,通过指针找到保存该文件所有的信息的数据结构。

套接字API里有个函数socket,它就是用来创建一个套接字。套接字设计的总体思路是,单个系统调用就可以创建任何套接字,因为套接字是相当笼统的。一旦套接字创建后,应用程序还需要调用其他函数来指定具体细节。
在这里插入图片描述

文件描述符:
在linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。

文件指针:
C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄。

二、socket函数解析

socket常用函数:
参数具体含义可以在linux环境下man func

//tcp and udp
int socket(int domain, int type, int protocol)
int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
int listen(int sockfd, int backlog)

//only tcp
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

int shutdown(int sockfd,int howto); 
int close(int fd)

//tcp
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

//udp
int sendto (socket s , const void * msg, int len, unsigned int flags,const struct sockaddr * to , int tolen );
int recvfrom(int sockfd,void *buf,int len,unsigned int lags,struct sockaddr *from,int *fromlen);  

//tcp and udp
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

//select
int select(int maxfdp, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

//epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

socket()函数

int  socket(int protofamily, int type, int protocol);   //返回sockfd

protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议

bind()函数

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
ipv4对应的是:
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

Unix域对应的是: 
#define UNIX_PATH_MAX    108
struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

addrlen:对应的是地址的长度。

listen()、connect()、accept()函数

int listen(int sockfd, int backlog);

listen函数的第一个参数即为要监听的socket描述字
第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect函数的第一个参数即为客户端的socket描述字
第二参数为服务器的socket地址
第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

sockfd:套接口描述字,该套接口在listen()后监听连接。
addr:传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小

shutdown()、close()函数

int shutdown(int sockfd, int howto);   

sockfd是需要关闭的socket的描述符。
how允许为shutdown操作选择以下几种方式:
SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作
SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。
shutdown可直接关闭描述符,不考虑描述符的参考数,可选择中止一个方向的连接。

int close(int sockfd);   
调用close()函数来释放该socket,从而停止在该socket上的任何数据操作

select()函数

int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

maxfd:  需要监视的最大的文件描述符的值+1 ;
rdset:  需要检测的可读文件描述符的集合;
wrset:  需要检测的可写文件描述符的集合;
exset:  需要检测的异常文件描述符的集合,不包括错误;
struct timeval: 用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0

fd_set的对象
FD_ZERO(fd_set *fdset) //将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_SET(fd_set *fdset) //用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(fd_set *fdset) //用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fdset) //用于测试指定的文件描述符是否在该集合中。

struct timeval{
     __time_t tv_sec;        /* Seconds. */
     __suseconds_t tv_usec;  /* Microseconds. */
};

epoll()函数

int epoll_create(int size)

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;
struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。
maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

Epoll的2种工作方式-水平触发(LT)和边缘触发(ET)

getsockopt()、setsockopt() 函数

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t*optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区。
optlen:optval缓冲区长度。

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区。
optlen:optval缓冲区长度。

其中optname:
SO_DEBUG,打开或关闭调试信息。
当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置 SOCK_DBG(10)位,或清SOCK_DBG位。
SO_REUSEADDR,打开或关闭地址复用功能。
当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为10。
SO_DONTROUTE,打开或关闭路由查找功能。
当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。
SO_BROADCAST,允许或禁止发送广播数据。
当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。
SO_SNDBUF,设置发送缓冲区的大小。
发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小 作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。
SO_RCVBUF,设置接收缓冲区的大小。
接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。
SO_KEEPALIVE,套接字保活。
如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时 器,否则关闭保活定时器。对于所有协议,该操
作都会根据option_value置或清 sock->sk->sk_flag中的 SOCK_KEEPOPEN位。
SO_OOBINLINE,紧急数据放入普通数据流。
该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。
SO_NO_CHECK,打开或关闭校验和。
该操作根据option_value的值,设置sock->sk->sk_no_check。
SO_PRIORITY,设置在套接字发送的所有包的协议定义优先权。Linux通过这一值来排列网络队列。
这个值在06之间(包括06),由option_value指定。赋给sock->sk->sk_priority。
SO_LINGER,如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。
该选项的参数(option_value)是一个linger结构:
struct linger {
    int   l_onoff;   
    int   l_linger;  
};
如果linger.l_onoff值为0(关闭),则清 sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为 linger.l_linger。

SO_PASSCRED,允许或禁止SCM_CREDENTIALS 控制消息的接收。
该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。

SO_TIMESTAMP,打开或关闭数据报中的时间戳接收。
该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打开,则还需设sock->sk->sk_flag中的SOCK_TIMESTAMP位,同时,将全局变量
netstamp_needed加1。

SO_RCVLOWAT,设置接收数据前的缓冲区内的最小字节数。
在Linux中,缓冲区内的最小字节数是固定的,为1。即将sock->sk->sk_rcvlowat固定赋值为1。

SO_RCVTIMEO,设置接收超时时间。
该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。

SO_SNDTIMEO,设置发送超时时间。
该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。

SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。
该选项最终将设备赋给sock->sk->sk_bound_dev_if。

SO_ATTACH_FILTER和SO_DETACH_FILTER。

三、UDP Client 与Server

在这里插入图片描述

1.client

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

#define MAX_READ_LINE 1024

int udpclient(int port) {
    int ret = 0;
    const char *server_ip_addr = "127.0.0.1";
    int server_ip_port = port;
 
    //要发送给server的数据
    const char *send_message = "quit";

    //用于存储接收到的数据
    char buff[MAX_READ_LINE] = {0};

    int socket_fd = -1;
    int recv_len = -1;
    do{
        //创建client端的socket套接口
        socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (socket_fd < 0) {
            fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //初始化sockaddr_in结构体
        struct sockaddr_in u_sockaddr;
        memset(&u_sockaddr, 0, sizeof(struct sockaddr_in));
        u_sockaddr.sin_family = AF_INET;
        u_sockaddr.sin_port = htons(server_ip_port);
        inet_pton(AF_INET, server_ip_addr, &u_sockaddr.sin_addr);

        socklen_t server_len=sizeof(u_sockaddr);

        //向server发送数据
        if(sendto(socket_fd, send_message, strlen(send_message), 0, (struct sockaddr *) &u_sockaddr, sizeof(struct sockaddr_in)) < 0){
            fprintf(stderr, "send message error: %s errno : %d", strerror(errno), errno);
            ret = -1;
            break;
        }
        fprintf(stdout, "client----> udp client send msg to server: %s\n", send_message); 

        //取server发送数据
        recv_len = recvfrom(socket_fd, buff,MAX_READ_LINE, 0, (struct sockaddr *)&u_sockaddr, &server_len);

        buff[recv_len] = '\0';
        fprintf(stdout, "client----> udp client recv msg from server: %s\n", buff); 
    }while(false);

 
    //关闭套接字
    if (socket_fd >= 0){
        close(socket_fd);
    }
    socket_fd = -1;
 
    return ret;
}

2.server

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAX_READ_LINE 1024

int udpserver(int port) {
    
    int ret = 0;
    int recv_len = -1;
    int server_ip_port = port;
 
    //用于存储接收到的数据
    char buff[MAX_READ_LINE] = {0};
 
    //初始化sockaddr_in结构体
    struct sockaddr_in u_sockaddr;
    memset(&u_sockaddr, 0, sizeof(u_sockaddr));      //bzero(&u_sockaddr,sizeof(u_sockaddr));
    u_sockaddr.sin_family = AF_INET;
    u_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    u_sockaddr.sin_port = htons(server_ip_port);

    socklen_t socklen = sizeof(u_sockaddr);
    //创建server端的socket套接字
    int server_fd = -1;
    do{
        server_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (server_fd < 0) {
            fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //绑定
        ret = bind(server_fd,(struct sockaddr *) &u_sockaddr,sizeof(u_sockaddr));
        if (ret < 0) {
            fprintf(stderr, "bind socket error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //接收来自客户端的链接请求
        for(;;) {
            //读取客户端数据到buff中
            recv_len = recvfrom(server_fd, buff, MAX_READ_LINE, 0, (struct sockaddr *)&u_sockaddr, &socklen);
            if (recv_len < 0) {
                fprintf(stderr, "recv error %s errno: %d\n", strerror(errno), errno);
                continue;
            }
    
            buff[recv_len] = '\0';
            //将接收到的数据标准输出
            fprintf(stdout, "server----> udp server recv msg from client: %s\n", buff);

            //将数据发送到客户端
            sendto(server_fd, buff,MAX_READ_LINE, 0, (struct sockaddr *)&u_sockaddr, socklen);
            fprintf(stdout, "server----> udp server send msg to client: %s\n", buff);

            //解决tcp服务不退出的问题
            if(strcmp(buff,"quit") == 0){
                fprintf(stdout, "server----> udp server will quit\n");
                break;
            }
        }
    }while(false);
 
    //关闭套接字
    if (server_fd >= 0){
        close(server_fd);
    }
    server_fd = -1;
 
    return 0;
}

四、TCP Client 与Server

在这里插入图片描述

1.client

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

#define MAX_READ_LINE 1024
int tcpclient(int port) {
    int ret = 0;
    const char *server_ip_addr = "127.0.0.1";
    int server_ip_port = port;
 
    //要发送给server的数据
    const char *send_message = "quit";

    //用于存储接收到的数据
    char buff[MAX_READ_LINE] = {0};

    int socket_fd = -1;
    int recv_len = -1;
    do{
        //创建client端的socket套接口
        socket_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (socket_fd < 0) {
            fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //初始化sockaddr_in结构体
        struct sockaddr_in t_sockaddr;
        memset(&t_sockaddr, 0, sizeof(struct sockaddr_in));
        t_sockaddr.sin_family = AF_INET;
        t_sockaddr.sin_port = htons(server_ip_port);
        inet_pton(AF_INET, server_ip_addr, &t_sockaddr.sin_addr);
    
        //连接
        if((connect(socket_fd, (struct sockaddr*)&t_sockaddr, sizeof(struct sockaddr))) < 0 ) {
            fprintf(stderr, "connect error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //向server发送数据
        if((send(socket_fd, send_message, strlen(send_message), 0)) < 0) {
            fprintf(stderr, "send message error: %s errno : %d", strerror(errno), errno);
            ret = -1;
            break;
        }
        fprintf(stdout, "client----> tcp client send msg to server: %s\n", send_message); 

        //取server发送数据
        recv_len = recv(socket_fd,buff,MAX_READ_LINE,0);
        buff[recv_len] = '\0';
        fprintf(stdout, "client----> tcp client recv msg from server: %s\n", buff); 
    }while(false);
 
    //关闭套接字
    if (socket_fd >= 0){
        close(socket_fd);
    }
    socket_fd = -1;
 
    return ret;
}

2.server

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
 
#define MAX_READ_LINE 1024
 
int tcpserver(int port) {
    int recv_len = -1;
    int conn_fd = -1;
    int ret = 0;
 
    int server_ip_port = port;
 
    //用于存储接收到的数据
    char buff[MAX_READ_LINE] = {0};
 
    //初始化sockaddr_in结构体
    struct sockaddr_in t_sockaddr;
    memset(&t_sockaddr, 0, sizeof(t_sockaddr));
    t_sockaddr.sin_family = AF_INET;
    t_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    t_sockaddr.sin_port = htons(server_ip_port);
 
    //创建server端的socket套接字
    int listen_fd = -1;
    do{
        listen_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (listen_fd < 0) {
            fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //绑定
        ret = bind(listen_fd,(struct sockaddr *) &t_sockaddr,sizeof(t_sockaddr));
        if (ret < 0) {
            fprintf(stderr, "bind socket error %s errno: %d\n", strerror(errno), errno);
            ret = -1;
            break;
        }
    
        //监听
        ret = listen(listen_fd, 1024);
        if (ret < 0) {
            fprintf(stderr, "listen error %s errno: %d\n", strerror(errno), errno);
            ret = -1;            
            break;
        }
    
        //接收来自客户端的链接请求
        for(;;) {
            conn_fd = accept(listen_fd, (struct sockaddr*)NULL, NULL);
            if(conn_fd < 0) {
                fprintf(stderr, "accpet socket error: %s errno :%d\n", strerror(errno), errno);
                continue;
            }
    
            //读取数据到buff中
            recv_len = recv(conn_fd, buff, MAX_READ_LINE, 0);
            if (recv_len < 0) {
                fprintf(stderr, "recv error %s errno: %d\n", strerror(errno), errno);
                continue;
            }
    
            buff[recv_len] = '\0';
            //将接收到的数据标准输出
            fprintf(stdout, "server----> tcp server recv msg from client: %s\n", buff);

            //将接收到的数据发送到客户端
            send(conn_fd,(void*)buff,MAX_READ_LINE,0);
            fprintf(stdout, "server----> tcp server send msg to client: %s\n", buff);

            close(conn_fd);
            conn_fd = -1;

            //解决tcp服务不退出的问题
            if(strcmp(buff,"quit") == 0){
                fprintf(stdout, "server----> tcp server will quit\n");
                break;
            }
        }
    }while(false);
    buff[0] = '\0';
    //关闭套接字
    if (listen_fd >= 0){
        close(listen_fd);
    }
    listen_fd = -1;
 
    return 0;
}

总结

通过上面的例子,应该对普通socket模型有了基本的理解,不过对与tcp udp编程来说,就是个皮毛。

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的TCP socket通信的C代码示例: 服务器端代码: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 int main() { int server_fd, new_socket, valread; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[1024] = {0}; char *hello = "Hello from server"; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt failed"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(server_fd, 3) < 0) { perror("listen failed"); exit(EXIT_FAILURE); } if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) { perror("accept failed"); exit(EXIT_FAILURE); } valread = read(new_socket, buffer, 1024); printf("%s\n", buffer); send(new_socket, hello, strlen(hello), 0); printf("Hello message sent\n"); return 0; } ``` 客户端代码: ```c #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 <arpa/inet.h> #define PORT 8080 int main(int argc, char const *argv[]) { int sock = 0, valread; struct sockaddr_in serv_addr; char *hello = "Hello from client"; char buffer[1024] = {0}; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Socket creation error \n"); return -1; } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { printf("\nInvalid address/ Address not supported \n"); return -1; } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\nConnection Failed \n"); return -1; } send(sock, hello, strlen(hello), 0); printf("Hello message sent\n"); valread = read(sock, buffer, 1024); printf("%s\n", buffer); return 0; } ``` 这个示例演示了一个简单的客户端和服务器之间的TCP socket通信。 UDP socket通信的C代码示例: 服务器端代码: ```c #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 <arpa/inet.h> #define PORT 8080 int main() { int sockfd; char buffer[1024]; struct sockaddr_in servaddr, cliaddr; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(PORT); if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } int len, n; len = sizeof(cliaddr); n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len); buffer[n] = '\0'; printf("Client : %s\n", buffer); sendto(sockfd, (const char *)buffer, strlen(buffer), MSG_CONFIRM, (const struct sockaddr *)&cliaddr, len); printf("Hello message sent.\n"); return 0; } ``` 客户端代码: ```c #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 <arpa/inet.h> #define PORT 8080 int main() { int sockfd; char buffer[1024]; struct sockaddr_in servaddr; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); servaddr.sin_addr.s_addr = INADDR_ANY; int n, len; printf("Enter message : "); scanf("%[^\n]%*c", buffer); sendto(sockfd, (const char *)buffer, strlen(buffer), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr)); printf("Hello message sent.\n"); n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&servaddr, &len); buffer[n] = '\0'; printf("Server : %s\n", buffer); close(sockfd); return 0; } ``` 这个示例演示了一个简单的客户端和服务器之间的UDP socket通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c+猿辅导

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值