linux socket的基础
linux的空间分为kernel space 和 user space, 比例是1:3
linux中一切皆文件, 所以用文件描述符来表示socket, 实际上是socket函数的返回值
代码语言:javascript复制EXAMPLE
An example of the use of socket() is shown in (3).
accept(2), bind(2), connect(2), fcntl(2), getpeername(2), getsockname(2), getsockopt(2), ioctl(2), listen(2), read(2), recv(2), select(2), send(2), shutdown(2), socketpair(2),
write(2), getprotoent(3), ip(7), socket(7), tcp(7), udp(7), unix(7)
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
extern int socket (int __domain, int __type, int __protocol) __THROW;
建立socket 是什么
socket是提供tcp/ip操作的一个api, 相当于封装tcp和ip, 用来进程与进程之间的通信
socket如何创建
一个进程要创建socket的流程如下所示
代码语言:javascript复制1. 调用socket函数创建socket, 返回一个文件描述符sfd
2. 调用bind函数绑定sfd
3. listen(sfd,...)
4. 当有client要建立socket连接, 接收来的连接 并返回一个客户端文件描述符cfd = accept(sfd,...)
5. 调用read()函数, 等待read client的输入
代码语言:javascript复制 #include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MY_SOCK_PATH "/somepath"
#define LISTEN_BACKLOG 50
#define handle_error(msg)
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
/*
* sfd - server fd
cfd - client fd
*/
int sfd, cfd;
struct sockaddr_un my_addr, peer_addr;
socklen_t peer_addr_size;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
handle_error("socket");
memset(&my_addr, 0, sizeof(struct sockaddr_un));
/* Clear structure */
my_addr.sun_family = AF_UNIX;
strncpy(my_addr.sun_path, MY_SOCK_PATH,
sizeof(my_addr.sun_path) - 1);
if (bind(sfd, (struct sockaddr *) &my_addr,
sizeof(struct sockaddr_un)) == -1)
handle_error("bind");
if (listen(sfd, LISTEN_BACKLOG) == -1)
handle_error("listen");
/* Now we can accept incoming connections one
at a time using accept(2) */
peer_addr_size = sizeof(struct sockaddr_un);
cfd = accept(sfd, (struct sockaddr *) &peer_addr,
&peer_addr_size);
if (cfd == -1)
handle_error("accept");
/* Code to deal with incoming connection(s)... */
/* When no longer required, the socket pathname, MY_SOCK_PATH
should be deleted using unlink(2) or remove(3) */
}
如何在linux上查看socket
代码语言:javascript复制 ll /proc/<pid>/fd
linux默认的三个文件描述符
0 - stdin
1 - stdout
2 - stderr
>& 是重定向到文件描述符
基于socket的应用
java nio
java nio用channel代替了输入输出流, 这个channel, 就是socket
其原理是使用了linux kernel里的fcntl
代码语言:javascript复制
/* Do the file control operation described by CMD on FD.
The remaining arguments are interpreted depending on CMD.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int fcntl (int __fd, int __cmd, ...);
多路复用select
复用的意思时不用每个进程/线程来操控单独的一个IO,只需一个进程/线程来操控多个IO
但是缺点是
1. 每循环一次都要传递很多fd, 调用一次select
代码语言:javascript复制SELECT(2)
Linux Programmer's Manual
SELECT(2)
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 5;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1)
perror("select()");
else if (retval)
printf("Data is available now.n");
/* FD_ISSET(0, &rfds) will be true. */
else
printf("No data within five seconds.n");
exit(EXIT_SUCCESS);
}
epoll模型
代码语言:javascript复制epoll - I/O event notification facility
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Set up listening socket, 'listen_sock' (socket(),
bind(), listen()) */
epollfd = epoll_create(10);
if (epollfd == -1) {
perror("epoll_create");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_pwait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &local, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
When used as an edge-triggered interface, for performance reasons, it is possible to add the file descriptor inside the epoll interface (EPOLL_CTL_ADD) once by specifying
(EPOLLIN|EPOLLOUT). This allows you to avoid continuously switching between EPOLLIN and EPOLLOUT calling epoll_ctl(2) with EPOLL_CTL_MOD.
References
https://www.bilibili.com/video/BV1V7411F7NB?from=search&seid=7973277497050423363
未完待续.......