热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linuxjavasocket编程_linuxsocket编程总结

在internet网络的世界里,socket可以说是最重要的任务间通讯的方式,尤其是当两个任务驻留在不同的机器上需要通过网络介质连接。今天系统复习一下s

在internet网络的世界里,socket可以说是最重要的任务间通讯的方式,尤其是当两个任务驻留在不同的机器上需要通过网络介质连接。今天系统复习一下socket编程,因为本人已经有了基本的网络和操作系统的知识,直接跳过很基本的背景知识介绍了。我理解的socket就是抽象封装了传输层以下软硬件行为,为上层应用程序提供进程/线程间通信管道。就是让应用开发人员不用管信息传输的过程,直接用socket API就OK了。贴个TCP的socket示意图体会以下。

0fbdfd012138acb7c466fbc31ff32e9d.png

Socket通信过程和API全解析

udp和TCP socket通信过程基本上是一样的,只是调用api时传入的配置不一样,以TCP client/server模型为例子看一下整个过程。

25b188694058dd42646e34489d1485fa.png

socket API

socket: establish socket interface

gethostname: obtain hostname of system

gethostbyname: returns a structure of type hostent for the given host name

bind: bind a name to a socket

listen: listen for connections on a socket

accept: accept a connection on a socket

connect: initiate a connection on a socket

setsockopt: set a particular socket option for the specified socket.

close: close a file descriptor

shutdown: shut down part of a full-duplex connection

1. socket()

#include /* See NOTES */

#include

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

- 参数说明

domain: 设定socket双方通信协议域,是本地/internet ip4 or ip6

Name Purpose Man page

AF_UNIX, AF_LOCAL Local communication unix(7)

AF_INET IPv4 Internet protocols ip(7)

AF_INET6 IPv6 Internet protocols ipv6(7)

type: 设定socket的类型,常用的有

SOCK_STREAM - 一般对应TCP、sctp

SOCK_DGRAM - 一般对应UDP

SOCK_RAW -

protocol: 设定通信使用的传输层协议

常用的协议有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,可以设置为0,系统自己选定。注意protocol和type不是随意组合的。

socket() API是在glibc中实现的,该函数又调用到了kernel的sys_socket(),调用链如下。

96b01689133b90c2a6fad0665d27fff7.png

详细的kernel实现我没有去读,大体上这样理解。调用socket()会在内核空间中分配内存然后保存相关的配置。同时会把这块kernel的内存与文件系统关联,以后便可以通过filehandle来访问修改这块配置或者read/write socket。操作socket就像操作file一样,应了那句unix一切皆file。提示系统的最大filehandle数是有限制的,/proc/sys/fs/file-max设置了最大可用filehandle数。当然这是个linux的配置,可以更改,方法参见Increasing the number of open file descriptors,有人做到过1.6 million connection。

2. bind()

#include /* See NOTES */

#include

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

参数说明

sockfd:之前socket()获得的file handle

addr:绑定地址,可能为本机IP地址或本地文件路径

addrlen:地址长度

功能说明

bind()设置socket通信的地址,如果为INADDR_ANY则表示server会监听本机上所有的interface,如果为127.0.0.1则表示监听本地的process通信(外面的process也接不进啊)。

3. listen()

#include /* See NOTES */

#include

int listen(int sockfd, int backlog);

参数说明

sockfd:之前socket()获得的file handle

backlog:设置server可以同时接收的最大链接数,server端会有个处理connection的queue,listen设置这个queue的长度。

功能说明

listen()只用于server端,设置接收queue的长度。如果queue满了,server端可以丢弃新到的connection或者回复客户端ECONNREFUSED。

4. accept()

#include /* See NOTES */

#include

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

参数说明:

addr:对端地址

addrlen:地址长度

功能说明:

accept()从queue中拿出第一个pending的connection,新建一个socket并返回。

新建的socket我们叫connected socket,区别于前面的listening socket。

connected socket用来server跟client的后续数据交互,listening socket继续waiting for new connection。

当queue里没有connection时,如果socket通过fcntl()设置为 O_NONBLOCK,accept()不会block,否则一般会block。

疑问:kernel是如何区分listening socket和connected socket的呢??虽然二者的五元组是不一样的,kernel如何知道通过哪个socket跟APP交互?通过解析内容,是SYN还是数据?暂时存疑。

5. connect()

#include /* See NOTES */

#include

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

参数说明:

sockfd: socket的标示filehandle

addr:server端地址

addrlen:地址长度

功能说明:

connect()用于双方连接的建立。

对于TCP连接,connect()实际发起了TCP三次握手,connect成功返回后TCP连接就建立了。

对于UDP,由于UDP是无连接的,connect()可以用来指定要通信的对端地址,后续发数据send()就不需要填地址了。

当然UDP也可以不使用connect(),socket()建立后,在sendto()中指定对端地址。

代码示例

TCP server端

这是TCP server代码例子,server收到client的任何数据后再回返给client。主进程负责accept()新进的connection并创建子进程,子进程负责跟client通信。

#include

#include

#include

#include

#include

#include

#include

#define MAXLINE 4096 /*max text line length*/

#define SERV_PORT 3000 /*port*/

#define LISTENQ 8 /*maximum number of client connections */

int main (int argc, char **argv) {

int listenfd, connfd, n;

socklen_t clilen;

char buf[MAXLINE];

struct sockaddr_in cliaddr, servaddr;

//creation of the socket

listenfd = socket (AF_INET, SOCK_STREAM, 0);

//preparation of the socket address

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons(SERV_PORT);

// bind address

bind (listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

// connection queue size 8

listen (listenfd, LISTENQ);

printf("%s\n","Server running...waiting for connections.");

while(1) {

clilen = sizeof(cliaddr);

cOnnfd= accept (listenfd, (struct sockaddr *) &cliaddr, &clilen);

printf("%s\n","Received request...");

if (!fork()) { // this is the child process

close(listenfd); // child doesn't need the listener

while ( (n = recv(connfd, buf, MAXLINE,0)) > 0) {

printf("%s","String received from and resent to the client:");

puts(buf);

send(connfd, buf, n, 0);

if (n <0) {

perror("Read error");

exit(1);

}

}

close(connfd);

exit(0);

}

}

//close listening socket

close (listenfd);

}

TCP client端

TCP端代码,单进程。client与server建立链接后,从标准输入得到数据发给server并等待server的回传数据并打印输出,然后等待标准输入...

#include

#include

#include

#include

#include

#include

#include

#define MAXLINE 4096 /*max text line length*/

#define SERV_PORT 3000 /*port*/

int main(int argc, char **argv)

{

int sockfd;

struct sockaddr_in servaddr;

char sendline[MAXLINE], recvline[MAXLINE];

//basic check of the arguments

if (argc !=2) {

perror("Usage: TCPClient

exit(1);

}

//Create a socket for the client

//If sockfd<0 there was an error in the creation of the socket

if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) <0) {

perror("Problem in creating the socket");

exit(2);

}

//Creation of the socket

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

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr= inet_addr(argv[1]);

servaddr.sin_port = htons(SERV_PORT); //convert to big-endian order

//Connection of the client to the socket

if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))<0) {

perror("Problem in connecting to the server");

exit(3);

}

while (fgets(sendline, MAXLINE, stdin) != NULL) {

send(sockfd, sendline, strlen(sendline), 0);

if (recv(sockfd, recvline, MAXLINE,0) == 0){

//error: server terminated prematurely

perror("The server terminated prematurely");

exit(4);

}

printf("%s", "String received from the server: ");

fputs(recvline, stdout);

}

exit(0);

}

高并发socket -- select vs epoll

上面举的server的例子是用多进程来实现并发,当然还有其他比较高效的做法,比如IO复用。select和epoll是IO复用常用的系统调用,详细分析一下。

select API

#include

#include

#include

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

//fd_set类型示意

typedef struct

{

unsigned long fds_bits[1024 / 64]; // 8bytes*16=128bytes

} fd_set;

参数说明:

readfds: 要监控可读的sockets集合,看是否可读

writefds:要监控可写的sockets集合,看是否可写

exceptfds:要监控发生exception的sockets集合,看是否有exception

nfds:上面三个sockets集合中最大的filehandle+1

timeout:阻塞的时间,0表示不阻塞,null表示无限阻塞

功能说明:

调用select()实践上是往kernel注册3组sockets监控集合,任何一个或多个sockets ready(状态跳变,不可读变可读 or 不可写变可写 or exception发生),

函数就会返回,否则一直block直到超时。

返回值>0表示ready的sockets个数,0表示超时,-1表示error。

epoll API

epoll由3个函数协调完成,把整个过程分成了创建,配置,监控三步。

step1 创建epoll实体

#include

int epoll_create(int size);

参数说明:

size:随便给个>0的数值,现在系统不care了。

功能说明:

epoll_create()在kernel内部分配了一块内存并关联到文件系统,函数调用成功会返回一个file handle来标识这块内存。

#include

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

Step2 配置监控的socket集合

#include

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *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 */

};

参数说明:

epfd:前面epoll_create()创建实体的标识

op:操作符,EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL

fd:要监控的socket对应的file handle

event:要监控的事件链表

功能说明:

epoll_ctl()配置要对哪个socket做什么样的事件监控。

step3 监控sockets

#include

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数说明:

epfd:epoll实体filehandle标识

events:指示发生的事情。application分配一块内存用event指针来指向,epoll_wait()调用时kernel将发生的事件存入event这块内存。

maxevents:最大可接收多少event

timeout:超时时间,0表示立即返回,函数不block,-1表示无限block。

功能说明:

epoll_wait()真正开始监控之前设置好的sockets集合。如果有事件发生,通过事件链表的方式返回给application。

对比select和epoll

有了上面的API,我们可以比较直观的比较select和epoll的特点

select的memory copy比epoll多。

select每次调用都要有用户空间到kernel空间的内存copy,把所有要监控配置copy到内核。

epoll只需要epoll_ctl配置的时候copy,而且是增量copy,epoll_wait没有用户空间到内核的copy

select函数调用返回后的处理比epoll低效

select()返回给application有几件事情发生了,但是没说是谁有事情,application还得挨个遍历过去,看看谁有啥事

epoll_wait()返回给application更多的信息,谁发生了什么事都通知给application了,application直接处理这些事件就行了,不需要遍历

select相比epoll有处理socket数量的限制

select内核限定了1024最大的filehandle数,如果要修改需要编译内核

epoll没有固定的限制,可以达到系统最大filehandle数

小结一下两者的对比,通常可以看到epoll的效率更高,尤其是在大量socket并发的时候。有人说在少量sockets,比如10多个以内,select要有优势,我没有验证过。不过这么少的并发用哪个都行,不会差别太大。

参考文章



推荐阅读
author-avatar
潮流时候男装
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有