socket函数需要指定IP协议是IPV4还是IPV6,分别对应AF_INET和AT_INET6,还需要指定是UDP还是TCP:TCP是基于数据流的,设为SOCK_STREAM;而UDP是基于数据报文的,设为SOCK_DGRAM.第三个参数为协议,传入0,系统会根据前两个参数给我们选择合适的协议族。
int client_TCP_fd = socket(AF_INET,SOCK_STREAM,0);
int client_UDP_fd = socket(AF_INET,SOCK_DGRAM,0);
基于TCP的Socket函数调用过程
基于TCP的socket流程.jpg客户端和服务端创建了socket之后,先调用bind()函数绑定IP和端口号。服务端有了IP和端口号,可以调用listen()函数进行监听,之后客户端可以发起连接。
内核为每个socket维护两个队列:已经建立连接额队列(三次握手完成,处于Established状态)和正在建立连接的队列(尚未完成三次握手,处于SYN_REVD状态)。
接下来服务端调用accept()函数,拿出已经完成的连接进行处理。
在服务端等待的时候,客户端可以通过connect()发起连接,在参数中指明要连接的IP和端口号,然后发起三次握手,内核会给客户端分配临时端口,握手成功服务端的accept()函数返回一个socket.注意监听socket和真正用来传数据的socket是两个。
连接成功建立,双方调用read()和write()开始读写数据。
client.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
int main()
{
int communacation_socket,ret;
struct sockaddr_in saddr;
communacation_socket = socket(AF_INET,SOCK_STREAM,0);
if(communacation_socket < 0)
{
perror("socket create error:");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(60000);
inet_pton(AF_INET,"192.168.1.140",&(saddr.sin_addr.s_addr));
ret = connect(communacation_socket,(struct sockaddr*)(&saddr),sizeof(saddr));
if(ret < 0)
{
perror("connect error:");
close(communacation_socket);
return -1;
}
printf("connect success!\n");
char str[1024] = "hello yisheng!";
ret = write(communacation_socket,str,strlen(str) + 1);
if(ret < 0)
{
perror("wtite error:");
close(communacation_socket);
return -1;
}
sleep(3);
memset(str,'\0',1024);
read(communacation_socket,str,1024);
printf("read_buf = %s\n",str);
close(communacation_socket);
return 0;
}
server.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
int main()
{
int listen_socket,ret;
int communacation_socket;
struct sockaddr_in saddr,caddr;
int addr_len;
char buf[1024];
char client_addr[24];
listen_socket = socket(AF_INET,SOCK_STREAM,0);
if(listen_socket < 0)
{
perror("socket create error:");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(60000);
inet_pton(AF_INET,"192.168.1.140",&(saddr.sin_addr.s_addr));
ret = bind(fd,(struct sockaddr*)(&saddr),sizeof(saddr));
if(ret < 0)
{
perror("bind error: ");
close(listen_socket);
return -1;
}
ret = listen(listen_socket,20);
if(ret < 0)
{
perror("listen error:");
close(listen_socket);
return -1;
}
while(1)
{
printf("accepting...\n");
communacation_socket = accept(fd,(struct sockaddr*)&caddr,&addr_len);
printf("accepting over...\n");
if(communacation_socket < 0)
{
perror("accept error:");
close(listen_socket);
return -1;
}
memset(client_addr,'\0',sizeof(client_addr));
strcpy(client_addr,inet_ntoa(caddr.sin_addr));
printf("client addresss: %s,and ",client_addr);
ret = read(communacation_socket,buf,1024);
if(ret < 0)
{
perror("read error:");
}
else
{
printf("buf = %s \n",buf);
}
memset(buf,'\0',1024);
strcpy(buf,"Succeed to receive!");
write(communacation_socket,buf,1024);
close(communacation_socket);
//close(fd);
communacation_socket = -1;
}
return 0;
}
socket在linux中以文件的形式存在,通过文件描述符写入和读出。在内核里面,socket是一个文件,对应有文件描述符。每一个进程都有一个数据结构task_struct,指向一个文件描述符数组,来列出这个进程打开的所有文件,文件描述符就是数组下标。数组中内容是指针,指向文件列表。既然是一个文件结构,就会有一个inode,真正的文件的inode是保存在磁盘上的,socket是保存在内存中的。inode指向内核中的Socket结构。
socket相关的内核数据结构.jpg基于UDP协议的socket编程
基于UDP的socket流程.jpgserver.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
int main()
{
int communacation_socket,ret;
struct sockaddr_in saddr,caddr;
int addr_len;
char buf[1024];
char client_addr[24];
communacation_socket = socket(AF_INET,SOCK_DGRAM,0);
if(listen_socket < 0)
{
perror("socket create error:");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(60000);
saddr.sin_addr.s_addr = INADDR_ANY;
ret = bind(communacation_socket,(struct sockaddr*)(&saddr),sizeof(saddr));
if(ret < 0)
{
perror("bind error: ");
close(communacation_socket);
return -1;
}
int len;
while(1)
{
while(1)
{
ret = recvfrom(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),&len);
if(ret > 0)
break;
}
printf("read_buf = %s\n",buf);
while(1)
{
memset(buf,'\0',1024);
strcpy(buf,"Succeed to receive!!!!");
ret = sendto(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),sizeof(saddr));
if(ret > 0)
break;
}
printf("write_buf = %s\n",buf);
}
close(communacation_socket);
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
int main()
{
int communacation_socket,ret;
int new_fd;
struct sockaddr_in saddr,caddr;
int addr_len;
char buf[1024];
char client_addr[24];
communacation_socket = socket(AF_INET,SOCK_DGRAM,0);
if(communacation_socket < 0)
{
perror("socket create error:");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(60000);
saddr.sin_addr.s_addr = INADDR_ANY;
int len;
while(1)
{
while(1)
{
memset(buf,'\0',1024);
strcpy(buf,"hello yisheng!!!!");
ret = sendto(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),sizeof(saddr));
if(ret > 0)
break;
}
printf("write_buf = %s\n",buf);
while(1)
{
ret = recvfrom(communacation_socket,buf,strlen(buf),0,(struct sockaddr*)(&saddr),&len);
if(ret > 0)
break;
}
printf("read_buf = %s\n",buf);
sleep(30);
}
close(communacation_socket);
return 0;
}
服务端如何接更多项目
socket的最大连接数,系统会用一个四元组来标识一个TCP连接。
{本机IP,本机端口,对端IP,对端端口}
服务器通常固定在某个端口处监听,等待客户端连接请求,对端IP固定的情况下,端口可变。最大TCP连接数 = 对端IP * 对端端口数。对于IPV4,IP最多232,端口最多216,因此理论上最大连接数为2^48。但是实际上远远达不到这个数字,原因在于(1)文件描述符数目的限制(2)操作系统内存有限。
查看用户级和系统级别的文件描述符限制数
Library liulongyang$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 256
pipe size (512 bytes, -p) 1
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1418
virtual memory (kbytes, -v) unlimited
liulongyang$ sysctl -a | grep maxfiles
kern.maxfiles: 49152
kern.maxfilesperproc: 24576
-
项目外包----->多进程。
相当于建立一个代理在监听来的请求。一旦一个一个连接建立,就会有一个连接scoket,可以创建一个子进程然后将基于连接的socket的交互交给新的子进程来做。创建子进程相关(基于linux):
进程复制.jpg
fork()函数------>父进程基础上完全copy一个子进程,复制文件描述符列表,复制内存空间,还会复制一条记录当前执行到哪一行程序的进程。根据fork()返回值区分到底是父进程还是子进程。根据返回的进程ID父进程可以查看子进程执行状态。
-
项目转包给独立项目组---->多线程。
Linux中通过pthread_create创建线程。不同之处在于虽然新的线程在task列表中会新建一项,但是很多资源还是共享的,例如文件描述符,只不过多了一个引用。C10K问题。如果新来一个TCP连接,就创建一个一个进程或线程。现实是一个操作系统并不能够创建很多的进程或者线程。
线程.jpg
-
一个项目组支撑多个项目---->IO多路复用。
一个项目组有多个项目了,这是需要每个项目组都有一个项目进度墙,每天提供项目进度墙查看每个项目的进度。如果某项目有了进展,就派人去看一下。
把所有的socket加入fd_set当中,fd_set作为项目墙,通过select()或者poll()函数监听文件描述符集合是否变化。一旦有变化,轮询所有的文件描述符,对fd_set中对应未为1的socket进行socket的读写操作。然后继续下一轮监听。
-
一个项目组支撑多个项目---->IO多路复用,从派人监督到有事通知。
缺点是能同时盯得的项目数收到FD_SETSIZE限制。于是诞生了和事件通知方式类似的IO多路复用机制--->epoll()机制:通过注册callback()函数,当某个文件描述符状态发生变化主动通知。
epoll()机制可以解决C10K问题。epoll()机制使得socket数量激增的时候,效率不会下降太多。并且可以监听的socket上限是相同定义的线程打开的最大描述符数。
epoll.jpg
网友评论