【socket编程】TCP服务器、UDP服务器、本地套接字【C语言代码实现】

这篇具有很好参考价值的文章主要介绍了【socket编程】TCP服务器、UDP服务器、本地套接字【C语言代码实现】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

0. 准备知识

0.1 大小端概念

0.2 网络字节序和主机字节序的转换

0.3 点分十进制串转换(IP地址转换函数)

0.4 IPV4结构体:(man 7 ip)

0.5 IPV6套接字结构体:(man 7 ipv6)

0.6 通用套接字结构体

1. 网络套接字函数

1.1 socket

1.2 connect

1.3 bind

1.4 listen

1.5 accept

1.6 端口复用

2. 包裹函数

2.1 wrap.c

2.2 wrap.h

3.TCP服务器

3.1 简单版

3.2 多进程版

3.3 多线程版

4. UDP服务器

5. 本地套接字

总结:


0. 准备知识

0.1 大小端概念

大端存储模式:是指数据的低位字节序保存在内存的高地址中,而数据的高位字节序保存在内存的低地址中
小端存储模式:是指数据的低位字节序保存在内存的低地址中,而数据的高位字节序保存在内存的高地址中

当以不同的存储方式,存储数据为0x12345678时:
c语言tcp udp,Linux网络编程,网络,TCP服务器,UDP服务器,c语言

0.2 网络字节序和主机字节序的转换

        TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。如果主机是大端字节序的,发送和接收都不需要做转换。同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。

        为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostlshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位,s表示16位。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

代码示例1:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    char buf[4] = {
        192, 168, 1, 2
    };
    unsigned int num = *(int*)buf;
    unsigned int sum = htonl(num);
    unsigned char* p = (unsigned char*)&sum;

    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    unsigned short a = 0x0102;
    unsigned short b = htons(a);
    printf("%#x\n", b);

    return 0;
}

执行截图:

c语言tcp udp,Linux网络编程,网络,TCP服务器,UDP服务器,c语言

 代码示例2:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    unsigned char buf[4] = {
        1, 1, 168, 192
    };
    int num = *(int*)buf;
    int sum = ntohl(num);
    unsigned char* p = (unsigned char*)&sum;

    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    return 0;
}

执行截图:

 c语言tcp udp,Linux网络编程,网络,TCP服务器,UDP服务器,c语言

0.3 点分十进制串转换(IP地址转换函数)

        我们通常见到的ip地址是字符串“192.168.1.2”这种类型的,需要进行转换才行。

#include <apra/inet.h>

//将点分十进制串转换成32位网络大端的数据

int inet_pton(int af, const char *src, void *dst);

支持IPV4和IPV6

参数:

        af:

                AF_INET:IPV4

                AF_INET6:IPV6

        src:点分十进制串的地址

        dst:存储32位网络数据的地址

返回值:

        成功:1

        失败:0

#include <apra/inet.h>

//将32位网络大端的数据转换成点分十进制串

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

        af:

                AF_INET:IPV4

                AF_INET6:IPV6

        src:32位网络数据的地址

        dst:存储点分十进制串的地址

        size:存储点分十进制串数组的大小(位数要具体决定)

                INET_ADDRSTRLEN 宏的值 16

返回值:

        存储点分十进制串的首地址

代码案例:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    char buf[] = "192.168.1.2";
    unsigned int num = 0;

    inet_pton(AF_INET, buf, &num);
    unsigned char* p = (unsigned char*)&num;
    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    char ip[16] = "";
    inet_ntop(AF_INET, &num, ip, 16);
    printf("%s\n", ip);

    return 0;
}

执行截图:

c语言tcp udp,Linux网络编程,网络,TCP服务器,UDP服务器,c语言

网络通讯解决三大问题:协议,IP,端口

0.4 IPV4结构体:(man 7 ip)

 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 */
           };

 /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

0.5 IPV6套接字结构体:(man 7 ipv6)

 struct sockaddr_in6 {
               sa_family_t     sin6_family;   /* AF_INET6 */
               in_port_t       sin6_port;     /* port number */
               uint32_t        sin6_flowinfo; /* IPv6 flow information */
               struct in6_addr sin6_addr;     /* IPv6 address */
               uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */
           };

           struct in6_addr {
               unsigned char   s6_addr[16];   /* IPv6 address */
           };

0.6 通用套接字结构体

struct sockaddr {
    sa_family_t sa_family; // 地址族
    char sa_data[14]; // 地址数据
};

注意:通常用以下形式

struct sockaddr_in addr;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

1. 网络套接字函数

1.1 socket

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

功能:创建套接字

参数:

        domain:
                AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
                AF_INET6 与上面类似,不过是来用IPv6的地址
                AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
        type :
                SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输
                SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
                SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
                SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
                SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol :

返回值:

        成功:文件描述符

        失败:-1

1.2 connect

#include <sys/types.h>

#include <sys/socket.h>

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

功能:连接服务器

参数:

        sockfd:socket文件描述符

        addr:ipv4套接字结构体的地址,含IP地址和端口号

        addrlen:ipv4套接字结构体的长度

返回值:

        成功:0

        失败:-1

1.3 bind

#include <sys/types.h>

#include <sys/socket.h>

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

功能:bind绑定

参数:

        sockfd:socket文件描述符

        addr:ipv4套接字结构体,含IP地址和端口号

        addrlen:ipv4套接字结构体的大小

返回值:

        成功:0

        失败:-1

1.4 listen

#include <sys/types.h>

#include <sys/socket.h>

int listen(int sockfd, int backlog);

功能:listen监听

参数:

        sockfd:socket文件描述符

        backlog:已完成队列和未完成队列里数之和的最大值 128

                        查看:cat /proc/sys/net/ipv4/tcp_max_syn_backlog 

返回值:

        成功:0

        失败:-1

1.5 accept

#include <sys/types.h>

#include <sys/socket.h>

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

功能:从已完成连接队列提取新的连接(如果没有新的连接,accept会阻塞)

参数:

        sockfd:套接字

        addr:ipv4套接字结构体

        addrlen:ipv4套接字结构体的大小的地址

返回值:

        成功:新的已连接套接字的文件描述符

        失败:-1

1.6 端口复用

在server代码的socket和bind调用之间插入如下代码:

int opt = 1;

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 

注意:程序中设置某个端口重新使用,在这个之前的其他网络程序将不能使用这个端口 

2. 包裹函数

2.1 wrap.c

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

void perr_exit(const char* s)
{
    perror(s);
    exit(-1);
}

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
    int n;

again:
    if ((n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被信号中断和软件层次中断,不能退出
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}

int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int n;

    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");

    return n;
}
int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int n;

    if ((n = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
    int n;

    if ((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");

    return n;
}
ssize_t Read(int fd, void* ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ((n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)//如果是被信号中断,不应该退出
            goto again;
        else
            return -1;
    }
    return n;
}

ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ((n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");

    return n;
}

/*参三: 应该读取固定的字节数数据*/
ssize_t Readn(int fd, void* vptr, size_t n)
{
    size_t  nleft;              //usigned int 剩余未读取的字节数
    ssize_t nread;              //int 实际读到的字节数
    char* ptr;

    ptr = vptr;
    nleft = n;

    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        }
        else if (nread == 0)
            break;

        nleft -= nread;
        ptr += nread;
    }
    return n - nleft;
}

/*:固定的字节数数据*/
ssize_t Writen(int fd, const void* vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char* ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ((nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }

        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static ssize_t my_read(int fd, char* ptr)
{
    static int read_cnt;
    static char* read_ptr;
    static char read_buf[100];

    if (read_cnt <= 0) {
    again:
        if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR)
                goto again;
            return -1;
        }
        else if (read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;

    return 1;
}

ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, * ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ((rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;
        }
        else if (rc == 0) {
            *ptr = 0;
            return n - 1;
        }
        else
            return -1;
    }
    *ptr = 0;

    return n;
}

int tcp4bind(short port, const char* IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));
    if (IP == NULL) {
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }
    else {
        if (inet_pton(AF_INET, IP, &serv_addr.sin_addr.s_addr) <= 0) {
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    //端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    return lfd;
}

2.2 wrap.h

#ifndef __WRAP_H_                                                                                                                                         
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char* s);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);
int tcp4bind(short port, const char* IP);
#endif

3.TCP服务器

socket模型创建流程图:

c语言tcp udp,Linux网络编程,网络,TCP服务器,UDP服务器,c语言

3.1 简单版

client.c

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

#define SERVER_IP "192.168.0.105"
#define SERVER_PORT 8008
int main()
{
    //创建套接字
    int sock_fd;
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    //连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &addr.sin_addr);
    connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
    //读写数据
    char buf[1024] = "";
    while (1)
    {
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(sock_fd, buf, n);//发送数据
        n = read(sock_fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
    }
    //关闭
    close(sock_fd);

    return 0;
}

server.c

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

#define SERVER_PORT 8008
#define SERVER_IP "192.168.0.106"
#define BACKLOG 128
int main()
{
    //创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    //绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);
    //addr.sin_addr.s_addr = INADDR_ANY;  //绑定的是通配地址
    inet_pton(AF_INET, SERVER_IP, &addr.sin_addr.s_addr);
    bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    //监听
    listen(lfd, BACKLOG);
    //提取
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
    char ip[16] = "";
    printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
    //读写
    char buf[1024] = "";
    while (1)
    {
        bzero(buf, sizeof(buf));
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(cfd, buf, n);
        n = read(cfd, buf, sizeof(buf));
        printf("%s\n", buf);
    }
    //关闭
    close(lfd);
    close(cfd);

    return 0;
}

客户端和服务器启动后可以使用netstat命令查看链接情况:

netstat -apn|grep 6666

3.2 多进程版

server.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include "wrap.h"

#define SERVER_PORT 8000
#define SERVER_IP "192.168.0.106" 
#define BACKLOG 128

void free_process(int sig)
{
    pid_t pid;
    while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
    {
        printf("child pid = %d has exited\n", pid);
    }
}
void handle_client(int cfd)
{
    char buf[1024];
    ssize_t n;

    while ((n = read(cfd, buf, sizeof(buf))) > 0)
    {
        printf("from clent :%s\n", buf);
        if (write(cfd, buf, n) < 0)
        {
            perror("Fail to sedn response to client");
            Close(cfd);
            exit(1);
        }
    }

    if (n < 0)
    {
        perror("Fail to read from client");
    }
    printf("Client closed connection\n");
    Close(cfd);
    exit(0);
}
int main()
{
    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = free_process;
    if (sigaction(SIGCHLD, &act, NULL) < 0)
    {
        perror("fail to sigaction");
        exit(1);
    }
    //创建套接字
    int lfd = tcp4bind(SERVER_PORT, NULL);
    //监听
    Listen(lfd, BACKLOG);
    //提前
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);

    while (1)
    {
        char ip[16] = "";
        //提取连接
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
        printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
        //fork创建子进程
        pid_t pid;
        pid = fork();

        if (pid < 0)
        {
            perror("fail to fork");
            Close(cfd);
            continue;
        }
        else if (pid == 0)
        {
            Close(lfd);
            handle_client(cfd);
            break;
        }

        Close(cfd);
    }
    //关闭
    Close(lfd);

    return 0;
}

3.3 多线程版

server.c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include "wrap.h"
#include <arpa/inet.h>

typedef struct c_info {
    int cfd;
    struct sockaddr_in cliaddr;
}CINFO;


void* client_fun(void* arg)
{
    CINFO* info = (CINFO*)arg;
    char ip[16] = "";
    printf("new client ip =%s port =%d\n", inet_ntop(AF_INET, &(info->cliaddr.sin_addr.s_addr), ip, 16), ntohs(info->cliaddr.sin_port));

    while (1)
    {
        char buf[1024] = "";
        int count = 0;
        count = read(info->cfd, buf, sizeof(buf));
        if (count < 0)
        {
            perror("");
            break;
        }
        else if (count == 0)
        {
            printf("client close\n");
            break;
        }
        else
        {
            printf("%s\n", buf);
            write(info->cfd, buf, count);
        }
    }
    Close(info->cfd);
    free(info);
    pthread_exit(NULL);
}
int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        perr_exit("argc < 2\n ./a.out 8000\n");
    }
    pthread_attr_t attr;
    int s = pthread_attr_init(&attr);
    if (s != 0)
    {
        perr_exit("pthread_attr_init error");
    }
    s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (s != 0)
    {
        perr_exit("pthread_attr_setdetachstate error");
    }
    short port = atoi(argv[1]);
    int lfd = tcp4bind(port, NULL);

    Listen(lfd, 128);
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    CINFO* info;

    while (1)
    {
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
        char ip[16] = "";

        pthread_t pthid;
        info = (CINFO*)malloc(sizeof(CINFO));
        if (NULL == info)
        {
            perr_exit("malloc error");
        }
        info->cfd = cfd;
        info->cliaddr = cliaddr;
        pthread_create(&pthid, &attr, client_fun, info);
    }
    return 0;
}

4. UDP服务器

        相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。

        由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。

        与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:

  1. 服务器应用层设计流量控制,控制发送数据速度。
  2. 借助setsockopt函数改变接收缓冲区大小。如:

#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

C/S模型UDP:

c语言tcp udp,Linux网络编程,网络,TCP服务器,UDP服务器,c语言 

server.c

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

#define SERVER_PORT 8001
#define MAXLINE 1024

int main()
{
    int sockfd;
    char buf[MAXLINE];
    struct sockaddr_in seraddr, cliaddr;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    memset(&seraddr, 0, sizeof(seraddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERVER_PORT);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (const struct sockaddr*)&seraddr, sizeof(seraddr)) < 0)
    {
        perror("fail to bind");
        exit(1);
    }
    socklen_t len = sizeof(cliaddr);
    int n;

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        n = recvfrom(sockfd, buf, MAXLINE, MSG_WAITALL, (struct sockaddr*)&cliaddr, &len);
        if (n < 0)
        {
            perror("fail to recvfrom");
            break;
        }
        else
        {
            printf("From Client data:%s\n", buf);
            if (sendto(sockfd, buf, n, 0, (const struct sockaddr*)&cliaddr, len) == -1)
            {
                perror("fail to sendto");
                break;
            }
        }
    }

    close(sockfd);
    return 0;
}

 client.c

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

#define SERVER_PORT 8001
#define MAXLINE 1024    

int main()
{
    int sockfd;
    char buf[MAXLINE];
    struct sockaddr_in seraddr;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    memset(&seraddr, 0, sizeof(seraddr));

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);

    int n;
    socklen_t len = sizeof(seraddr);

    while (1)
    {
        n = read(STDIN_FILENO, buf, sizeof(buf));
        if(sendto(sockfd, buf, n, 0, (const struct sockaddr*)&seraddr, len) == -1)
        {
            perror("fail to sendto");
            break;
        }

        memset(buf, 0, sizeof(buf));
        n = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&seraddr, &len);
        if (n < 0)
        {
            perror("fail to recvfrom");
            break;
        }
        else
        {
            printf("From Server data:%s\n", buf);
        }
    }
    close(sockfd);

    return 0;
}

5. 本地套接字

        socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。

        UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIXDomain Socket通讯的。

        使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。

        UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

        对比网络套接字地址结构和本地套接字地址结构:

struct sockaddr_in {
    __kernel_sa_family_t sin_family;             /* Address family */      地址结构类型
        __be16 sin_port;                         /* Port number */        端口号
        struct in_addr sin_addr;                    /* Internet address */    IP地址
};
struct sockaddr_un {
    __kernel_sa_family_t sun_family;         /* AF_UNIX */            地址结构类型
        char sun_path[UNIX_PATH_MAX];         /* pathname */        socket文件名(含路径)
};

以下程序将UNIX Domain socket绑定到一个地址。 

size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->member) 

 service:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

char *socket_path = "/tmp/demo_socket";

int main(void) {
    struct sockaddr_un addr;
    char buf[100];
    int fd,cl,rc;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

    unlink(socket_path);

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind error");
        exit(-1);
    }

    if (listen(fd, 5) == -1) {
        perror("listen error");
        exit(-1);
    }

    while (1) {
        if ((cl = accept(fd, NULL, NULL)) == -1) {
            perror("accept error");
            continue;
        }

        while ((rc=read(cl,buf,sizeof(buf))) > 0) {
            printf("read %u bytes: %.*s\n", rc, rc, buf);
            write(cl, buf, rc);
        }
        if (rc == -1) {
            perror("read");
            exit(-1);
        }
        else if (rc == 0) {
            printf("EOF\n");
            close(cl);
        }
    }

    return 0;
}

client:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

char *socket_path = "/tmp/demo_socket";

int main(void) {
    struct sockaddr_un addr;
    char buf[100];
    int fd,rc;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect error");
        exit(-1);
    }

    while(1) {
        printf("Enter message to send: ");
        fgets(buf, sizeof(buf), stdin);
        if ((rc = write(fd, buf, strlen(buf))) > 0) {
            printf("Message sent\n");
            read(fd, buf, sizeof(buf));
            printf("Server replied : %s\n", buf);
        }
        else {
            printf("Error or connection closed\n");
            break;
        }
    }

    return 0;
}

总结:

        这些都是 C语言实现的代码,建议理解并自行敲出来。文章来源地址https://www.toymoban.com/news/detail-723398.html

到了这里,关于【socket编程】TCP服务器、UDP服务器、本地套接字【C语言代码实现】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux网络编程:Socket套接字编程(Server服务器 Client客户端)

    文章目录: 一:定义和流程分析 1.定义 2.流程分析  3.网络字节序 二:相关函数  IP地址转换函数inet_pton inet_ntop(本地字节序 网络字节序) socket函数(创建一个套接字) bind函数(给socket绑定一个服务器地址结构(IP+port)) listen函数(设置最大连接数或者说能同时进行三次握手的最

    2024年02月12日
    浏览(85)
  • Socket套接字编程(实现TCP和UDP的通信)

      🎉🎉🎉点进来你就是我的人了 博主主页:🙈🙈🙈戳一戳,欢迎大佬指点! 人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔 🦾🦾🦾 目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿 谢谢

    2024年02月03日
    浏览(91)
  • 【网络编程】网络套接字&udp通用服务器和客户端

    端口号(port)是传输层协议的内容: 端口号是一个2字节16位的整数(uint16) 端口号用来标识主机上的一个进程 IP地址+port能够标识网络上的某一台主机和某一个进程 一个端口号只能被一个进程占用 此处我们先对TCP(Transmission Control Protocol 传输控制协议) 有一个直观的认识,后面再

    2024年02月16日
    浏览(214)
  • 【网络编程】TCP流套接字编程(TCP实现回显服务器)

    Socket(既能给客户端使用,也能给服务器使用) 构造方法 基本方法: ServerSocket(只能给服务器使用) 构造方法: 基本方法: 客户端代码示例: 服务器代码示例: 运行结果: 代码执行流程: 服务器启动,阻塞在accept,等待客户端建立连接. 客户端启动.这里的new操作会触发和服务器之间建立连

    2024年04月25日
    浏览(66)
  • Java网络编程基础:TCP Socket套接字编程 IntAddress UDP等...

    目录 一,网络基础 1.IP地址 2.端口 3.TCP/UDP协议 4.网络编程开发模式  二,基于套接字的Java网络编程 1.Socket  2.InetAddress 三.基于TCP的Socket网络编程 1.单服务器端与单Socket客户端一次通讯 2.单服务器端接收多次通讯  3.TCP网络通讯补充 四,基于UDP的网络编程 1. DatagramSocket:收发

    2024年04月29日
    浏览(46)
  • 【计算机网络】网络编程套接字&UDP服务器客户端的简单模拟

    需要云服务器等云产品来学习Linux的同学可以移步/–腾讯云–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦) 每台主机都有自己的IP地址,当数据在进行通信的时候,除了要发送的数据外,在报头里面还要包含发送方的IP和接收方的IP,这里发送方的IP就

    2024年02月20日
    浏览(64)
  • 【Linux网络编程】网络编程套接字(TCP服务器)

    作者:爱写代码的刚子 时间:2024.4.4 前言:本篇博客主要介绍TCP及其服务器编码 只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换 字符串转in

    2024年04月14日
    浏览(79)
  • Java网络编程(二)Socket 套接字(TCP和UDP),以及TCP的回显

    我们软件工作者,着重编写的是应用层的代码,但是发送这个数据,我们就需要将应用层传输到传输层,也就意味着我们需要调用应用层的API,统称为 Socket API。 套接字的分类: 流套接字:使用传输层TCP协议 特点: 有连接:使用 TCP 通信的双方,需要时刻保存对方的相关消

    2024年02月09日
    浏览(54)
  • 【网络编程】网络编程概念,socket套接字,基于UDP和TCP的网络编程

    前言: 大家好,我是 良辰丫 ,今天我们一起来学习网络编程,网络编程的基本概念,认识套接字,UDP与TCP编程.💞💞💞 🧑个人主页:良辰针不戳 📖所属专栏:javaEE初阶 🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。 💦期待大家三连,关注

    2023年04月20日
    浏览(61)
  • 基于TCP/UDP的socket服务器搭建流程

    TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需 要建立连接 TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 TCP面向字节流,实际上是TCP把数据看成一连串

    2023年04月08日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包