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

news/2024/5/15 15:29:19/文章来源:https://blog.csdn.net/crr411422/article/details/131680574

目录

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"typedef struct c_info {int cfd;struct sockaddr_in cliaddr;
}CINFO;void* client_fun(void* arg);int main(int argc, char* argv[])
{if (argc < 2){printf("argc < 2\n ./a.out 8000 \n");return 0;}pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);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));info->cfd = cfd;info->cliaddr = cliaddr;pthread_create(&pthid, &attr, client_fun, info);}return 0;
}
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);
}

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

server.c

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>#define MAXLINE 80
#define SERV_PORT 8080int main(void)
{struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len = sizeof(cliaddr);char buf[MAXLINE];char str[INET_ADDRSTRLEN];int sockfd, i, n;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");return 1;}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");return 1;}printf("Accepting connections ...\n");while (1) {n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr*)&cliaddr, &cliaddr_len);if (n < 0) {perror("recvfrom error");continue;}printf("Received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));for (i = 0; i < n; i++)buf[i] = toupper((unsigned char) buf[i]);if (sendto(sockfd, buf, n, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr)) < 0)perror("sendto error");}if (close(sockfd) < 0) {perror("close error");return 1;}return 0;
}

 client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>#define MAXLINE 80
#define SERV_PORT 8080int main(int argc, char* argv[])
{struct sockaddr_in servaddr;int sockfd, n;char buf[MAXLINE];sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");return 1;}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);servaddr.sin_port = htons(SERV_PORT);while (fgets(buf, MAXLINE, stdin) != NULL) {n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));if (n < 0) {perror("sendto error");continue;}n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);if (n < 0) {perror("recvfrom error");continue;}if (write(STDOUT_FILENO, buf, n) < 0) {perror("write error");}}if (close(sockfd) < 0) {perror("close error");return 1;}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 <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
*/
int serv_listen(const char* name)
{int fd, len, err, rval;struct sockaddr_un un;/* create a UNIX domain stream socket */if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)return(-1);/* in case it already exists */unlink(name);/* fill in socket address structure */memset(&un, 0, sizeof(un));un.sun_family = AF_UNIX;strcpy(un.sun_path, name);len = offsetof(struct sockaddr_un, sun_path) + strlen(name);/* bind the name to the descriptor */if (bind(fd, (struct sockaddr*)&un, len) < 0) {rval = -2;goto errout;}if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */rval = -3;goto errout;}return(fd);errout:err = errno;close(fd);errno = err;return(rval);
}
int serv_accept(int listenfd, uid_t* uidptr)
{int clifd, len, err, rval;time_t staletime;struct sockaddr_un un;struct stat statbuf;len = sizeof(un);if ((clifd = accept(listenfd, (struct sockaddr*)&un, &len)) < 0)return(-1); /* often errno=EINTR, if signal caught *//* obtain the client's uid from its calling address */len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */un.sun_path[len] = 0; /* null terminate */if (stat(un.sun_path, &statbuf) < 0) {rval = -2;goto errout;}if (S_ISSOCK(statbuf.st_mode) == 0) {rval = -3; /* not a socket */goto errout;}if (uidptr != NULL)*uidptr = statbuf.st_uid; /* return uid of caller *//* we're done with pathname now */unlink(un.sun_path);return(clifd);errout:err = errno;close(clifd);errno = err;return(rval);
}
int main(void)
{int lfd, cfd, n, i;uid_t cuid;char buf[1024];lfd = serv_listen("foo.socket");if (lfd < 0) {switch (lfd) {case -3:perror("listen"); break;case -2:perror("bind"); break;case -1:perror("socket"); break;}exit(-1);}cfd = serv_accept(lfd, &cuid);if (cfd < 0) {switch (cfd) {case -3:perror("not a socket"); break;case -2:perror("a bad filename"); break;case -1:perror("accept"); break;}exit(-1);}while (1) {r_again:n = read(cfd, buf, 1024);if (n == -1) {if (errno == EINTR)goto r_again;}else if (n == 0) {printf("the other side has been closed.\n");break;}for (i = 0; i < n; i++)buf[i] = toupper(buf[i]);write(cfd, buf, n);}close(cfd);close(lfd);return 0;
}

client:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
/*
* Create a client endpoint and connect to a server.
* Returns fd if all OK, <0 on error.
*/
int cli_conn(const char* name)
{int fd, len, err, rval;struct sockaddr_un un;/* create a UNIX domain stream socket */if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)return(-1);/* fill socket address structure with our address */memset(&un, 0, sizeof(un));un.sun_family = AF_UNIX;sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);/* in case it already exists */unlink(un.sun_path);if (bind(fd, (struct sockaddr*)&un, len) < 0) {rval = -2;goto errout;}/* fill socket address structure with server's address */memset(&un, 0, sizeof(un));un.sun_family = AF_UNIX;strcpy(un.sun_path, name);len = offsetof(struct sockaddr_un, sun_path) + strlen(name);if (connect(fd, (struct sockaddr*)&un, len) < 0) {rval = -4;goto errout;}return(fd);
errout:err = errno;close(fd);errno = err;return(rval);
}
int main(void)
{int fd, n;char buf[1024];fd = cli_conn("foo.socket");if (fd < 0) {switch (fd) {case -4:perror("connect"); break;case -3:perror("listen"); break;case -2:perror("bind"); break;case -1:perror("socket"); break;}exit(-1);}while (fgets(buf, sizeof(buf), stdin) != NULL) {write(fd, buf, strlen(buf));n = read(fd, buf, sizeof(buf));write(STDOUT_FILENO, buf, n);}close(fd);return 0;
}

总结:

        这些都是 C语言实现的代码,建议理解并自行敲出来。

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

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

相关文章

实现跨语言互动:如何在Python中调用Java的JavaParser库解析Java源代码

1、背景 在多语言开发环境中&#xff0c;我们经常需要进行跨语言的操作。有时&#xff0c;我们可能会在Python环境下需要使用Java的库或者功能。这个博客将展示如何在Python中调用Java的JavaParser库来解析Java源代码。 2、需求 在许多软件开发场景中&#xff0c;我们可能需…

【算法与数据结构】239、LeetCode滑动窗口最大值

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;这道题我们如果用暴力破解法需要 O ( n ∗ k ) O(n*k) O(n∗k)的复杂度。思索再三&#xff0c;我们需要…

【新版系统架构】第十九章-大数据架构设计理论与实践

大数据处理系统架构 大数据处理系统面临挑战 如何利用信息技术等手段处理非结构化和半结构化数据如何探索大数据复杂性、不确定性特征描述的刻画方法及大数据的系统建模数据异构性与决策异构性的关系对大数据知识发现与管理决策的影响 大数据处理系统架构特征 鲁棒性和容错…

【基于FPGA的芯片设计】RISC-V的20条指令CPU设计

实验板卡&#xff1a;xc7a100tlc sg324-2L&#xff0c;共20个开关 实验要求&#xff1a;

危机现场 | 如果给你25万美元,你会登上泰坦号吗?

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 小黑 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩天津录音间 这是我们更名为记者下班后的第一期节目&#xff0c;临时把危机现场&#xff08;原为死神来了&#xff09…

音视频编码实战-------pcm+yuv数据转成MP4

文章目录 1.编码流程图2.相关模块及函数2.1 编码器相关API2.2 复用器相关API2.3 重采样相关API注意点 简单的编码流程相关代码 1.编码流程图 2.相关模块及函数 2.1 编码器相关API avcodec_find_encoder: 根据编码器ID查找编码器 avcodec_alloc_context3:创建编码器上下文 avc…

【Arduino小车实践】PID应用之四驱小车

一、 PID公式 二、 PID应用的必要性 1. 四驱小车运动 左边两个驱动轮和右边两个驱动轮的速度相同直线右边轮子的速度大于左边轮子的速度左偏右边轮子的速度小于左边轮子的速度 右偏 2. 产生多种运动的原因 小车的4个电机&#xff0c;减速箱以及车轮在物理层面上存在误差&am…

【文章系列解读】Nerf

1. Nerf NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 2020年8月3日 &#xff08;0&#xff09;总结 NeRF工作的过程可以分成两部分&#xff1a;三维重建和渲染。&#xff08;1&#xff09;三维重建部分本质上是一个2D到3D的建模过程&#xff…

两种传输层协议TCP和UDP【图解TCP/IP(笔记十二)】

文章目录 两种传输层协议TCP和UDPTCP与UDP区分UDP的特点及其目的TCP的特点及其目的 两种传输层协议TCP和UDP 在TCP/IP中能够实现传输层功能的、具有代表性的协议是TCP和UDP。 ■ TCP TCP是面向连接的、可靠的流协议。流就是指不间断的数据结构&#xff0c;你可以把它想象成排…

排序算法笔记-归并排序

归并排序 简介 通过找到中间值&#xff0c;然后递归分别从左区间和右区间找中间值&#xff0c;最终将所给的值划分为单个块&#xff0c;然后进行一步一步回溯&#xff0c;分块由两个单个分区排序后合成一个&#xff0c;以此类推&#xff0c;最后实现有序排序 时间复杂度 最…

计算机网关原理、子网掩码原理(路由器、交换机)(网关:与以太网接口关联的路由)

文章目录 网关网关的历史网关的功能网关的原理相关疑问为什么用子网掩码与IP地址进行与运算来确定一个IP地址所属的子网&#xff1f;网关地址是谁定的&#xff0c;是配置路由的人随意定的吗&#xff1f;&#xff08;配置人员定的&#xff09;如何正确设置网关地址&#xff08;路…

[MySQL]MySQL内置函数

[MySQL]MySQL内置函数 文章目录 [MySQL]MySQL内置函数1. 日期函数2. 字符串函数3. 数学函数4. 其他函数 1. 日期函数 常用日期函数如下&#xff1a; 函数名称描述current_date()获取当前日期current_time()获取当前时间current_timestamp()获取当前时间戳now()获取当前日期时…

无法将“pip“识别为cmdlet、函数、脚本文件或可运行程序的名称。

出现问题如下&#xff1a; 出现问题原因&#xff1a; 没有添加pip对应的安装目录进入环境变量里面的系统变量。 解决方案&#xff1a; 1.确定python的安装路径 将python的路径添加到系统变量中 2.输入pip所在的安装路径&#xff1a; python路径\Lib\site-packages 3.添加…

如何执行Photoshop脚本

环境 Photoshop: CC2017 OS: Windows 10 脚本放置位置 C:\Program Files\Adobe\Adobe Photoshop CC 2015\Presets\Scripts #也就是 PS的安装目录\Presets\Scripts

程序员,到美国!赚美元!!!

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Python 和 RabbitMQ 进行消息传递和处理

一、RabbitMQ 简介 RabbitMQ 是一个开源的消息代理软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;标准。它的官方客户端提供了多种编程语言的接口&#xff0c;包括 Python、Java 和 Ruby 等。它支持消息的持久化、多种交换机类型、消息通知机制、灵活…

Orange pi3初调试

因为树莓派沦为理财产品1年前出手殆尽后&#xff0c;现在唯一一个B性能不足一直没动力调试&#xff0c;沦为吃灰工具。 偶然之间多多给推了个orange产品预售&#xff0c;看了下pi3的参数&#xff0c;这不和赚了差价的3B一个性能吗&#xff1f;果断定了个预售款&#xff0c;在差…

2023年Unity面试题大全,共十万字面试题总结【收藏一篇足够面试,持续更新】

&#x1f388;前言 为了方便大家可以重点复习某个模块&#xff0c;所以将各方面的知识点进行了拆分并更新整理了新的内容&#xff0c;并对之前的版本中有些模糊的地方进行了纠正。此篇文章为Unity所有面试题模块的目录导航文章&#xff0c;全网最全的 Unity 面试题 都在这里了…

反转链表 (反转整个链表+反转部分链表)

简单问题&#xff1a;反转整个链表 定义一个函数&#xff0c;输入一个链表的头节点&#xff0c;反转该链表并输出反转后链表的头节点。 解题思路&#xff1a; 1.因为反转后链表的末尾节点是原链表的头节点&#xff0c;所以一开始将头节点的后驱保存起来&#xff1b; 2.将头节…

漏洞攻击 --- TCP -- 半开攻击、RST攻击

TCP半开攻击&#xff08;半连接攻击&#xff09; --- syn攻击 &#xff08;1&#xff09;定义&#xff1a; sys 攻击数据是DOS攻击的一种&#xff0c;利用TCP协议缺陷&#xff0c;发送大量的半连接请求&#xff0c;耗费CPU和内存资源&#xff0c;发生在TCP三次握手中。 A向B…