【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时:
在这里插入图片描述

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

执行截图:

 代码示例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;
}

执行截图:

 

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

执行截图:

网络通讯解决三大问题:协议,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;elseperr_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;elsereturn -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;elsereturn -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;elsereturn -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;elsereturn -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;}elsereturn -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模型创建流程图:

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 128void 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:

 

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 1024int 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语言实现的代码,建议理解并自行敲出来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/26505.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安卓和苹果页面和逻辑是否有必要追求百分之百统一

安卓和苹果存在操作系统系统差异&#xff0c;所以有些不一样。如&#xff1a;安卓的启动页面是根页面&#xff0c;并且可以设置显示时间&#xff1b;而苹果的启动页面是一个类似图片的容器&#xff08;Launch Screen.storyboard&#xff09;&#xff0c;不像其它页面可以控制页…

手搓一台简单的网络损伤仪——弱网测试

1、介绍 支持对链路带宽、传输时延、丢包率和无码率的手动设置&#xff1b; 1.1、网络损伤仪在使用时&#xff0c;网络拓扑连接 1.2、网络损伤仪管理页面展示 2、使用的设备及相关技术栈 一台Intel 赛扬 J1900的迷你主机【拥有4个千兆网口】&#xff1b;ubuntu-18.04.5-live…

Stable Diffusion - 高清局部重绘 (Inpaint) 调整脸部和手部细节

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131775232 在 Stable Diffusion 中&#xff0c;局部重绘(Inpaint)功能是一种可以让你在图像上删除不想要的区域&#xff0c;并用周围的像素自动填…

springCloud通过两种方式配置热更新

该热更新实际就是通过改动nacos官网里面的配置管理的妹纸内容实现 定义一个config包&#xff0c;在该包下面复制该代码 package cn.itcast.user.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.spring…

【LLM】Langchain使用[三](基于文档的问答)

文章目录 一、基于文档的问答1. 创建向量存储2. 不同类型的chain链 二、本地知识库问答1.1 整体框架2. 文本切分3. 图解流程 Reference 一、基于文档的问答 1. 创建向量存储 CSVLoader加载csv数据&#xff0c;loader结合模型使用使用Dock Array内存搜索向量存储&#xff0c;作…

PostgreSQL MVCC的弊端优化方案

我们之前的博客文章“我们最讨厌的 PostgreSQL 部分”讨论了大家最喜欢的 DBMS 多版本并发控制 (MVCC) 实现所带来的问题。其中包括版本复制、表膨胀、索引维护和真空管理。本文将探讨针对每个问题优化 PostgreSQL 的方法。 尽管 PostgreSQL 的 MVCC 实现是 Oracle 和 MySQL 等…

Jenkins动态化阶段步骤

Jenkins中如何去根据入参动态化阶段步骤呢&#xff1f; Groovy语言基础 定义一个列表变量 def list []定义一个map的kv结构变量 def map [:]如何可以动态化阶段步骤 动态化步骤&#xff1a;其实就是&#xff0c;在jenkins pipeline中根据入参或者其他变量列表&#xff0c;动…

微服务 云原生:gRPC 客户端、服务端的通信原理

gRPC Hello World protoc 是 Protobuf 的核心工具&#xff0c;用于编写 .proto 文件并生成 protobuf 代码。在这里&#xff0c;以 Go 语言代码为例&#xff0c;进行 gRPC 相关代码编写。 下载 protoc 工具&#xff1a;https://github.com/protocolbuffers/protobuf/releases&a…

【单谐波非线性振动问题求解器 GUI 】使用单个谐波表示解决 MDOF 非线性振动问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ceph存储的应用

ceph存储的应用 一&#xff1a;创建 CephFS 文件系统 MDS 接口1.服务端操作1&#xff09;在管理节点创建 mds 服务2&#xff09;查看各个节点的 mds 服务3&#xff09;创建存储池&#xff0c;启用 ceph 文件系统4&#xff09;查看mds状态&#xff0c;一个up&#xff0c;其余两个…

Java使用JNI实现C文件的调用

1.使用IDEA新建工程 构建最基本的maven类型就行&#xff0c;文件结构如下&#xff1a; 其中最主要的类如下&#xff1a; package org.linx;public class TestJNI {static {/*** 加载jni库&#xff0c;有一个重要的点就是生成的为libnative.so&#xff0c;下面加载代码需要消…

Python应用实例(二)数据可视化(四)

数据可视化&#xff08;四&#xff09;下载数据 1.CSV文件格式1.1 分析CSV文件头‘1.2 打印文件头及其位置1.3 提取并读取数据1.4 绘制温度图表1.5 在图表中添加日期 从网上下载数据&#xff0c;并对其进行可视化。网上的数据多得令人难以置信&#xff0c;大多未经仔细检查。如…