精华内容
下载资源
问答
  • 上一篇文章:【Linux】Linux网络编程(含常见服务器模型,上篇)。   高级嵌套字函数 前面介绍的一些函数(read、write等)都是网络程序里最基本的函数,也是最原始的通信函数。下面介绍一下几个网络编程的高级...

    上一篇文章:【Linux】Linux网络编程(含常见服务器模型,上篇)

     

    高级嵌套字函数

    前面介绍的一些函数(read、write等)都是网络程序里最基本的函数,也是最原始的通信函数。下面介绍一下几个网络编程的高级函数:

    recv()函数

    int recv(int s, void *buf, int len, unsigned int flags);

    函数说明:经socket接收数据,recv()用来接收远程主机指定的socket传来的数据,并把数据存到参数buf指向的内存空间,参数len为可接收数据的最大长度。参数flags一般设为0,其数值定义如下:

    MSG_OOB:接收以out-of-band送出的数据;MSG_PEEK:返回来的数据并不会在系统内删除,如果再调用recv()会返回相同的数据内容;MSG_WAITALL:强迫接收到len大小的数据后才能返回,除非有错误或信号发生;MSG_NOSIGNAL:此操作不愿被SIGPIPE信号中断。

    返回值:成功则返回接收到的字符数,失败则返回-1,错误码保存在errno中。

    send()函数

    int send(int s, const void *msg, int len, unsigned int flags);

    函数说明:经socket发送数据,send()用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,参数msg指向欲连线的数据内容,参数len则为数据长度,参数flags一般设为0,其数值定义如下:

    MSG_OOB:传送的数据以out-of-band送出;MSG_DONTROUTE:取消路由表查询;MSG_DONTWAIT:设置为不可阻断运作;MSG_NOSIGNAL:此动作不愿被SIGPIPE信号中断。

    返回值:成功则返回接收到的字符数,失败则返回-1。

    recvmsg()函数

    int recvmsg(int s, struct msghdr *msg, unsigned int flags);

    函数说明:经socket接收数据,recvmsg()用来接收远程主机指定的socket传来的数据。参数s为已建立好连线的socket,如果利用UDP协议,则不需要经过连线操作。参数msg指向欲年限的数据结构内容,参数glags一般设置为0。

    返回值:成功则返回接收到的字符数,失败则返回-1。

    sendmsg()函数

    int sendmsg(int s, struct msghdr *msg, unsigned int flags);

    函数说明:经socket发送数据,sendmsg()用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议,则不需要经过连线操作。参数msg指向欲连线的数据内容,参数len则为数据长度,参数flags一般设为0。

    返回值:成功则返回接收到的字符数,失败则返回-1。

    结构msghdr的定义如下:

    struct msghdr
    {
            void *msg_name;                //发送接收地址
            socklen_t msg_namelen;                //地址长度
            struct iovec *msg_iov;                //发送/接收数据量
            size_t msg_iovlen;                //元素个数
            void *msg_control;                //补充数据
            size_t msg_controllen;                //补充数据缓冲长度
            int msg_flags;                //接收消息标识
    };

    嵌套字的关闭

    关闭嵌套字有两个函数close()和shutdown(),用close()时和用户关闭文件类似。

    int shutdown(int s, int how);

    函数说明:终止socket通信,shutdown()用来终止参数s所指定的socket连线。参数how有三种情况:

    how=0:终止读取操作;how=1:终止传送操作;how=2:终止读取及传送操作。

    返回值:成功则返回0,失败则返回-1。

    close()和shutdown()的区别:

    • 对应的系统调用不同,close()函数对应的系统调用是sys_close(),在fs/open.c中定义。shutdown()函数对应的系统调用是sys_shutdown(),在net/socket.c中定义;
    • shutdown()只能用于套接字文件,close()可以用于所有文件类型;
    • shutdown()可以选择关闭全双工连接的读通道或者写通道,并没有释放文件描述符,close()会同时关闭全双工连接的读写通道,除了关闭连接外,还会释放套接字占用的文件描述符;
    • close()函数会关闭套接字ID,如果有多个进程共享一个套接字,close()每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close(),套接字将被释放。而shutdown()会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零。

     

    嵌套字选项

    有时用户要控制嵌套字的行为(如修改缓冲区的大小),这个时候用户就要控制嵌套字的选项了。

    getsockopt()函数

    int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);

    函数说明:取得socket状态,getsockopt()会将参数s所指定的socket状态返回。参数optname代表欲取得何种选项状态,而参数optval则指向欲保存结果的内存地址,参数optlen为该空间的大小。

    返回值:成功则返回0,若有错误则返回-1。

    例子:

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    
    int main()
    {
            int s,optval,optlen=sizeof(int);
            if((s=socket(AF_INET,SOCK_STREAM,0))<0)
                    perror("socket");
            getsockopt(s,SOL_SOCKET,SO_TYPE,&optval,&optlen);
            printf("optval=%d\n",optval);
            close(s);
            
            return 0;
    }

    setsockopt()函数

    int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

    函数说明:设置socket状态,setsockopt()用来设置参数s所指定的socket状态。参数level代表欲设置的网络层,一般设为SOL_SOCKET以存取socket层,参数optname代表欲设置的选项,有以下几种数值:

    SO_DEBUG:打开或关闭排错模式;SO_REUSEADDR:允许在bind()过程中本地地址可重复使用;SO_TYPE:返回socket形态;SO_ERROR:返回socket已发生的错误原因;SO_DONTROUTE:送出的数据包不要利用路由设备来传输;SO_BROADCAST:使用广播方式传送;SO_SNDBUF:设置送出的暂存区大小;SO_RCVBUF:设置接收的暂存区大小;SO_KEEPALIVE:定期确认连线是否已终止;SO_OOBINLINE:当接收到OOB数据时会马上送至标准输入设备;SO_LINGER:确保数据安全且可靠地传送出去。

    返回值:成功则返回0,若有错误则返回-1。

    ioctl()函数

    int ioctl(int handle, int cmd, [int *argdx, int argcx]);

    函数说明:ioctl()是设备驱动程序中对设备的IO通道进行管理的函数。所谓对IO通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等。它的调用格式如下:

    int ioctl(int fd, int cmd, ...);

    其中:fd就是用户程序打开设备时使用open()函数返回的文件标识符,cmd就是用户程序对设备的控制命令,后面的省略号是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl()函数是文件结构中的一个属性分量,也就是说,如果驱动程序提供了对ioctl()的支持,用户就能在用户程序中使用ioatl()函数控制设备的IO通道。

    返回值:成功则返回0,若有错误则返回-1。

     

    服务器模型

    循环服务器:UDP服务器

    UDP循环服务器的实现非常简单。UDP服务器每次从嵌套字上读取一个客户端的请求并处理,然后将结果返回给客户端。可以用下面的算法来实现:

    socket(...);
    bind(...);
    
    while(1){
            recvfrom(...);
            ...                //服务器处理函数
            sendto(...);
    }

    因为UDP是面向非连接的,没有一个客户端可以总是占住服务端,只要处理过程不是死循环,服务器对于每个客户端的请求总是能够满足。

    循环服务器:TCP服务器

    TCP服务器接收一个客户端的连接,然后处理,完成该客户端的所有请求后,断开连接。算法如下:

    socket(...);
    bind(...);
    listen(...);
    
    while(1){
            accept(...);
            
            while(1){
                    read(...);
                    ...                    //服务器处理函数
                    write(...);
            }
    }

    TCP循环服务器一次只能处理一个客户端的请求。只有在该客户端的所有请求都满足后,服务器才可以继续响应后面的请求。如果有一个客户端占住服务器不释放时,其他的客户端都不能工作了。因此,TCP服务器很少采用循环服务器模型。

    并发服务器:TCP服务器

    为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型。并发服务器的思想是,每一个客户端的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。算法如下:

    socket(...);
    bind(...);
    listen(...);
    
    while(1){
            accept(...);
    
            if(fork(...)==0){                //子进程
                    while(1){
                            read(...);
                            ...                //服务器处理函数
                            write(...);
                    }
                    close(...);
                    exit(...);
            }
    
            close(...);
    }

    TCP并发服务器可以解决TCP循环服务器中客户端独占服务器的情况。不过也同时带来一个较大的问题,为了响应客户端的请求,服务器要创建子进程来处理,而创建子进程是一个非常消耗资源的操作。

    并发服务器:多路复用IO

    为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用IO模型。最常用的函数为select。

    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    函数说明:IO多工机制,select()用来等待文件描述符状态的改变。参数n代表最大的文件描述符加1,参数readfds、writefds、exceptfds称为描述词组,是用来回传该描述词的读、写或例外的状况,参数timeout为结构timeval,用来设置select()的等待时间。下列宏提供了处理这三种描述词组的方式:

    FD_CLR:用来清除描述词组set中相关fd的位;FD_ISSET:用来测试描述词组set中相关fd的位是否为真;FD_SET:用来设置描述词组set中相关fd的位;FD_ZERO:用来清除描述词组set全部的位。

    返回值:如果参数timeout设为NULL则表示select()没有timeout。

    常见的程序片段为fs_set readset:

    FD_ZERO(&readset);
    FD_SET(fd,&readset);
       
    select(fd+1,&readset,NULL,NULL,NULL);
    if(FD_ISSET(fd,readset)){
            ...
    }

    使用select后用户的服务器程序就变成:

    初始化(socket,bind,listen);
    
    while(1){
            设置监听读写文件描述符(FD_*);
            调用select;
            
            如果倾听嵌套字就绪,就说明一个新的连接请求建立{
                    建立连接(accept);
                    加入到监听文件描述符中去;
            }否则说明是一个已经连接过的描述符{
                    进行操作(read或者write);
            }
    }

    多路复用可以解决资源限制的问题。该模型实际上是将UDP循环模型用在了TCP上面。这也就带来了一些问题,比如,由于服务器依次处理客户端的请求,所以可能会导致有的客户端会等待很久。

    并发服务器:UDP服务器

    人们把并发的概念用于UDP就得到了并发UDP服务器模型。并发UDP服务器模型其实很简单。和并发的TCP服务器模型类似,都是创建一个子进程来处理。

    除非服务器处理客户端请求所用的时间比较长,人们实际上很少使用这种模型。

     

    展开全文
  • 在网络上,标志一台计算机可以用名字形式的网址,例如blog.csdn.net/qq_38410730,也可以使用地址的IP形式47.95.164.112,它是一个32位的整数,每个网络节点一个IP地址,它唯一地确定一台主机,但一台主机可以多...

    基本数据结构介绍

    Linux系统是通过提供嵌套字(socket)来进行网络编程的。网络程序通过socket和其他几个函数的调用,会返回一个通用的文件描述符,用户可以将这个描述符看成普通的文件的描述符来操作,这就是Linux的设备无关性的好处。用户可以通过向描述符的读写操作实现网络之间的数据交流。

    表示套接口的socket结构体

    struct socket {
    	socket_state		state;            //指明套接口的连接状态
    	short			type;
    	unsigned long		flags;
    
    	struct fasync_struct	*fasync_list;
    	wait_queue_head_t	wait;
    
    	struct file		*file;            //指向sockfs文件系统中的相应文件
    	struct sock		*sk;            //任何协议族都有其特定的套接口特性,该域就指向特定协议族的套接口对象
    	const struct proto_ops	*ops;            //指明可对套接口进行的各种操作
    };

    描述套接口通用地址的数据结构sockaddr结构体

    由于历史的缘故,在bind、connect等系统调用中,特定于协议的套接口地址结构指针都要强制转换成该通用的套接口地址结构指针。结构形式如下:

    struct sockaddr {
    	sa_family_t	sa_family;	/* address family, AF_xxx	*/
    	char		sa_data[14];	/* 14 bytes of protocol address	*/
    };

    描述因特网地址结构的数据结构sockaddr_in(这里局限于IP4)

    struct sockaddr_in {
            sa_family_t		sin_family;	    /* 描述协议族		*/
            __be16		sin_port;	        /* 端口号			*/
            struct in_addr	sin_addr;	    /* 因特网地址		*/
    
            unsigned char		__pad[__SOCK_SIZE__ - sizeof(short int) -
                    sizeof(unsigned short int) - sizeof(struct in_addr)];
    };

     

    基本网络函数介绍

    表头文件

    #include <sys/types.h>
    #include <sys/socket.h>

    socket()函数

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

    函数说明:socket()用来建立一个新的socket,也就是向系统注册,通知系统建立一通信端口。

    • 参数domain指定协议域,又称为协议族。常用的协议族有:AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址,完整的定义在usr/include/bits/socket.h内;
    • 参数type指定socket类型。常用的socket类型有:SOCK_STREAM(双向连续且可信的数据流,即TCP)、SOCK_DGRAM(不连续不可信赖的数据包连接)、SOCK_PACKET(和网络驱动程序直接通信)、SOCK_SEQPACKET(连续可信赖的数据包连接)等等;
    • 参数protocol指定协议。常用的协议有:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

    返回值:成功则返回socket描述符,失败则返回-1。

    bind()函数

    int bind(int sockfd, struct sockaddr * my_addr, int addrlen);

    函数说明:bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

    • 参数sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket;
    • 参数addr:一个指向sockaddr类型对象的指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同;
    • 参数addrlen:对应的是地址的长度。

    返回值:成功则返回0,失败则返回-1。

    对于不同socket的sockaddr,定义了一个通用的数据结构:

    struct sockaddr {
    	sa_family_t	sa_family;	/* address family, AF_xxx	*/
    	char		sa_data[14];	/* 14 bytes of protocol address	*/
    };

    其中,sa_family为调用socket()时的domain参数,即AF_xxxx值;sa_data最多使用14个字符长度。

    sockaddr结构会因使用不同的socket domain而有不同的结构定义,例如AF_INET:

    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 */
    };
    
    struct in_addr {
            uint32_t       s_addr;     /* address in network byte order */
    };

    例如AF_INET6:

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

    listen()函数

    int listen(int sockfd, int backlog)

    函数说明:sockfd参数为要监听的socket描述字,backlog参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen()函数将socket变为被动类型的,等待客户的连接请求。

    返回值:成功则返回0,失败则返回-1。

    注意:listen()只适合SOCK_STREAM或SOCK_SEQPACKET的socket类型。如果socket为AF_INET,则参数backlog的最大值为128。backlog不能限制连接的个数,只能限制后备连接(连接请求队列)的大小;一旦调用accept()函数处于请求队列里面的后备连接的数量就减一。

    connect()函数

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

    函数说明:sockfd参数为客户端的socket描述字,addr参数为服务器的socket地址,addrlen参数为socket地址的长度。客户端通过调用connect()函数来建立与TCP服务器的连接。

    返回值:成功则返回0,失败返回-1,错误原因存于errno中。

    accept()函数

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

    函数说明:sockfd参数为服务器的socket描述字,addr参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。

    返回值:如果accpet()成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

    注意:accept()的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept()函数返回的是已连接的socket描述字。一个服务器通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

     

    服务器与客户端的信息函数

    字节转换函数

    区分一下网络字节序与主机字节序:

    主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

    • Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;
    • Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    网络字节序:4个字节的32bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

    所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。

    为了统一,在Linux下有专门的字节转换函数:

    #include <netinet/in.h>
    
    unsigned long int htonl(unsigned long int hostlong);        //将32位主机字节序转换成网络字节序
    unsigned short int htons(unsigned short int hostshort);        //将16位主机字节序转换成网络字节序
    unsigned long int ntohl(unsigned long int netlong);        //将32位网络字节序转换成主机字节序
    unsigned short int ntohs(unsigned short int netshort);        //将16位网络字节序转换成主机字节序

    在这四个转换函数中,h代表host,n代表network,l代表long,s代表short。

    IP和域名的转换

    在网络上,标志一台计算机可以用名字形式的网址,例如blog.csdn.net/qq_38410730,也可以使用地址的IP形式47.95.164.112,它是一个32位的整数,每个网络节点有一个IP地址,它唯一地确定一台主机,但一台主机可以有多个IP地址。

    在网络中,通常组织运行多个名字服务器来提供名字与IP地址之间的转换,各种应用程序通过调用解析器库中的函数来与域名服务系统通信。常用的解析函数有:

    struct hostent * gethostbyname(const char * hostname);            //名字地址转换为数字地址
    struct hostent * gethostbyaddr(const char * addr, int len, int type);            //数字地址转换为名字地址

    函数说明:第二个函数sddr参数为含有IP地址信息的in_addr结构的指针(为了同时传递IPv4之外的其他信息,设置为char*类型的);len参数为地址信息的字节数,IPv4为4,IPv6为16;type参数为地址族信息,IPv4为AF_INET,IPv6为AF_INET6。

    返回值:两个函数失败时返回NULL且设置h_errno错误变量,调用h_strerrno()可以得到详细的错误信息。

    其中,struct hostent的定义为:

    struct hostent
    {
            char *h_name;         //正式主机名
            char **h_aliases;     //主机别名
            int h_addrtype;       //主机IP地址类型:IPV4-AF_INET
            int h_length;		  //主机IP地址字节长度,对于IPv4是四字节,即32位
            char **h_addr_list;	  //主机的IP地址列表
    };
    	
    #define h_addr h_addr_list[0]   //保存的是IP地址
    

    例如:

    #include <stdio.h>
    #include <netdb.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
     
    int main(int argc, char *argv[])
    {
    	char *ptr, **pptr;
    	struct hostent *hptr;
    	char str[32] = {0};
    	ptr = argv[1];
    	
    	if((hptr = gethostbyname(ptr)) == NULL){
    		printf("gethostbyname error: %s\n", ptr);
    		return 0;
    	}
    	
    	printf("official hostname:%s\n", hptr->h_name);   //主机规范名
    	for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)   //将主机别名打印出来
    		printf("alias: %s\n", *pptr);
    	
    	switch(hptr->h_addrtype)  //根据地址类型,将地址打印出来
    	{
    		case AF_INET:
    		case AF_INET6:
    			pptr = hptr->h_addr_list;
    		
    			for(; *pptr != NULL; pptr++)   //将得到的所有地址打印出来
    			{
    				printf("address: %s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));   //inet_ntop: 将网络字节序的二进制转换为文本字符串的格式
    				printf("first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
    			}
    			break;
    		default:
    			printf("unkown address type\n");
    			break;
    	}
    	
    	return 0;
    }

    字符串的IP和32位的IP转换

    网络上用的IP地址都是数字加点构成的,而在struct in_addr结构中用的是32位的IP,把数字加点类型转换成32位IP,可以使用下面两个函数:

    int inet_aton(const char * cp, struct in_addr * inp);        //将数字加点类型转化成32位的IP,存储在inp指针里
    char * inet_ntoa(struct in_addr in);        //将32位的IP转换成数字加点类型

    函数中的a表示ASCII,n代表network。

    注意与下面两个函数的区别:

    in_addr_t inet_addr(const char *cp);        //将数字加点类型转化成32位的IP
    in_addr_t inet_network(const char *cp);        //将数字加点类型转化成32位的IP

    这两个函数与inet_aton()的区别在于:

    这两个函数,当IP是255.255.255.255时,会认为这是个无效的IP地址,这是历史遗留问题,其实在目前大部分的路由器上,这个255.255.255.255的IP都是有效的。而inet_aton()函数认为255.255.255.255是有效的,它不会冤枉这个看似特殊的IP地址。

    服务信息函数

    在网络程序中,用户有时需要知道端口IP和服务信息,这个时候可以使用以下几个函数:

    int getsockname(int sockfd, struct sockaddr *localaddr, int *addrlen);
    int getpeername(int sockfd, struct sockaddr *peeraddr, int *addrlen);
    
    struct servent *getservbyname(const char *servname, const char *protoname);            //某一个协议下的某个服务
    struct servent *getservbyport(int port, const char *protoname);
    
    struct servent
    {
            char *s_name;            //正式服务名
            char **s_aliases;            //别名列表
            int s_port;            //端口号
            char *s_proto;            //使用的协议
    }

    例子:

    struct servent *sptr;
    
    sptr = getservbyname("domain", "udp");        // DNS using UDP
    sptr = getservbyname("ftp", "tcp");        //FTP using TCP
    
    sptr = getservbyport(htons(53), "udp");        // DNS using UDP
    sptr = getservbyport(htons(21), "tcp");        //FTP using TCP
    
    

    参考文章:网络编程学习笔记(getservbyname和getservbyport函数)

     

    完整的读写函数

    一旦用户建立了连接,下一步就是进行通信了。在Linux下,把用户前面建立的通道看作文件描述符,这样服务器端和客户端进行通信时,只要往文件描述符里面读写东西就可以了,就像用户往文件读写一样。

    #include <unistd.h>
    
    ssize_t write(int fd, const void *buf, size_t count);
    ssize_t read(int fd, void *buf, size_t count);

    函数说明:将数据写入已打开的文件内,write()会把参数buf所指的内存写入count个字节到参数fd所指向的文件内。如果顺利,write()会返回实际写入的字节数,当有错误发生时,则返回-1;

    从已打开的文件读取数据,read()会把参数fd所指向的文件传送count个字节到buf指针所指的内存中。返回值为实际读取到的字节数。

    例子:

    //客户端向服务器端写
    struct my_struct my_struct_client;
    write(fd, (void *)&my_struct_client, sizeof(struct my_struct));
    
    //服务器端的读
    char buffer[sizeof(struct my_struct)];
    struct *my_struct_server;
    read(fd, (void *)buffer, sizeof(struct my_struct));
    my_struct_server=(struct my_struct *)buffer;

    需要注意的是:在网络上传递数据时,用户一般都是把数据转化为char类型的数据来传递。接收时也是一样。同时,用户没有必要在网络上传递指针(因为传递指针时没有任何意义的,用户必须传递指针所指向的内容)。

    这里还要注意一下堵塞的问题:

    在阻塞模式下, 对于TCP套接字(默认情况下),当使用 write() 发送数据时:

    • 首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write() 函数继续写入数据;
    • 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write() 也会被阻塞,直到数据发送完毕缓冲区解锁,write() 才会被唤醒;
    • 如果要写入的数据大于缓冲区的最大长度,那么将分批写入;
    • 直到所有数据被写入缓冲区 write() 才能返回。

    当使用 read() 读取数据时:

    • 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来;
    • 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read() 函数再次读取;
    • 直到读取到数据后 read() 函数才会返回,否则就一直被阻塞。

    这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。

     

    用户数据报发送

    之前的主要是基于TCP协议的网络程序,下面就主要介绍一下基于UDP协议的网络程序。

    表头文件

    #include <sys/types.h>
    #include <sys/socket.h>

    recvfrom()函数

    int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

    函数说明:经socket接收数据,recvfrom()用来接收远程主机指定的socket传来的数据,并把数据存到参数buf指向的内存空间,参数len为可接收数据的最大长度。参数flags一般设为0,参数from用来指定欲传送的网络地址,参数fromlen为sockaddr的结构长度。

    返回值:成功则返回接收到的字符数,失败则返回-1。

    sendto()函数

    int sendto(int s, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);

    函数说明:经socket发送数据,sendto()用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连接线的数据内容,参数flags一般设为0,参数to用来指定欲传送的网络地址,参数tolen为sockaddr的结构长度。

    返回值:成功则返回接收到的字符数,失败则返回-1。

     

    下一篇文章:【Linux】Linux网络编程(含常见服务器模型,下篇)

     

    展开全文
  • 简单介绍了常见的三种服务器模型,并给出阻塞式服务器的示例代码

    辛苦堆砌,转载请注明出处,谢谢!


            上一篇简单介绍了Socket的原理,并通过简单的程序,实现了UDP传输数据,本篇文章则主要说明以下常见的几种服务器模型。目前常见的服务器模型主要有三种:阻塞服务器,并发服务器以及异步服务器。三种形式各有利弊,下面介绍一下。

            阻塞式服务器是最好实现的服务器,也是问题最多的服务器,上一篇文章中的示例代码就是典型的阻塞式服务器。客户端发送到服务器的请求,服务器会进行排队,依次处理请求。前一个请求没有处理完成,服务器不会处理后面的请求。这种服务器很容易进行攻击,只需要向服务器发送一个处理时间很长的请求,就会将其他的请求堵在门外,导致其他请求无法得到处理,所以,这种服务器更多的是作为理论模型,实际应用并不多。

            第二种是并发式服务器,这种服务器处理请求时,每接收到一个请求,就启动一个线程处理该请求,这种模式的服务器,好处是不会出现阻塞式服务器请求被拥堵的情况,但是也是存在问题的,服务器启动线程是有一定的开销的,请求数量不多的时候,服务器启动线程没有问题,但是请求过多时,将会导致服务器的资源耗尽。所以,会存在一种方式——建立线程池来处理请求,每当请求到来时,向线程池申请线程进行处理,这样,线程池开放多少线程是固定的,不会导致系统资源耗尽,但是依然会有一些问题,当线程池被占用满时,还是有可能出现请求被阻塞的情况,所以这种方式是一种折中的方式。但是,对于并发请求不是很多的场景来说, 使用这种方式是完全可以的。

            第三种方式是异步服务器,使用该种方式,一般要借助于系统的异步IO机制,如select或poll,这种方式,当一个请求到达时,我们可以先将请求注册,当有数据可以读取时,会得到通知,这时候我们处理请求,这样,服务器进程没有必要阻塞处理,也不会存在很大的系统开销,因此,目前对于并发量要求比较高的服务器,一般都是采用这种方式。

          本文先给出基于TCP的阻塞式服务器,后面两篇文章再先后给出其他两种服务器的实现示例。示例还是基于前一篇文章的功能,服务器将字符串转换为大写,首先给出客户端的代码,后面的几种服务器模型介绍,也是用这个客户端,只是将一些服务器开放的常量替换即可。

    package com.yjp.client;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    
    import com.yjp.server.ToUpperTCPBlockServer;
    
    public class ToUpperTCPClient {
    	
    	//客户端使用的TCP Socket
    	private Socket clientSocket;
    	
    	public String toUpperRemote(String serverIp, int serverPort, String str) {
    		StringBuilder recvStrBuilder = new StringBuilder();
    		try {
    			//创建连接服务器的Socket
    			clientSocket = new Socket(serverIp, serverPort);
    			
    			//写出请求字符串
    			OutputStream out = clientSocket.getOutputStream();
    			out.write(str.getBytes());
    			
    			//读取服务器响应
    			InputStream in = clientSocket.getInputStream();
    			for (int c = in.read(); c != '#'; c = in.read()) {
    				recvStrBuilder.append((char)c);
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				if (clientSocket != null) {
    					clientSocket.close();
    				}
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		
    		return recvStrBuilder.toString();
    	}
    	
    	public static void main(String[] args) {
    		ToUpperTCPClient client = new ToUpperTCPClient();
    		String recvStr = client.toUpperRemote(ToUpperTCPBlockServer.SERVER_IP, ToUpperTCPBlockServer.SERVER_PORT, 
    				"aaaAAAbbbBBBcccCCC" + ToUpperTCPBlockServer.REQUEST_END_CHAR);
    		System.out.println("收到:" + recvStr);
    	}
    }
    

    相关的内容,注释已经写得很清楚,关键的在于main函数中发送时,需要最后叠加一个服务器请求终结符,上一篇文章有提到过,由于TCP传输数据是流模式,没有数据边界,服务器端只有拿到终结符,才认为一个请求发送完成,否则会一直等待读取(除非设置超时)。下面是服务器的代码

    package com.yjp.server;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    public class ToUpperTCPBlockServer {
    	
    	//服务器IP
    	public static final String SERVER_IP = "127.0.0.1";
    	
    	//服务器端口号
    	public static final int SERVER_PORT = 10005;
    	
    	//请求终结字符串
    	public static final char REQUEST_END_CHAR = '#';
    	
    	/***
    	 * 启动服务器
    	 * @param 服务器监听的端口号,服务器ip无需指定,系统自动分配
    	 */
    	public void startServer(String serverIP, int serverPort) {
    		
    		//创建服务器地址对象
    		InetAddress serverAddr;
    		try {
    			serverAddr = InetAddress.getByName(serverIP);
    		} catch (UnknownHostException e1) {
    			e1.printStackTrace();
    			return;
    		}
    		
    		//Java提供了ServerSocket作为服务器
    		//这里使用了Java的自动关闭的语法,很好用
    		try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT, 5, serverAddr)) {
    			while (true) {
    				StringBuilder recvStrBuilder = new StringBuilder();
    				
    				//有客户端向服务器发起tcp连接时,accept会返回一个Socket
    				//该Socket的対端就是客户端的Socket
    				//具体过程可以查看TCP三次握手过程
    				try (Socket connection = serverSocket.accept()) {
    					InputStream in = connection.getInputStream();
    						
    					//读取客户端的请求字符串,请求字符串以#终结
    					for (int c = in.read(); c != REQUEST_END_CHAR; c = in.read()) {
    						recvStrBuilder.append((char)c);
    					}
    					recvStrBuilder.append('#');
    					
    					String recvStr = recvStrBuilder.toString();
    					
    					//向客户端写出处理后的字符串
    					OutputStream out = connection.getOutputStream();
    					out.write(recvStr.toUpperCase()getBytes());
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	public static void main(String[] args) {
    		ToUpperTCPBlockServer server = new ToUpperTCPBlockServer();
    		server.startServer(SERVER_IP, SERVER_PORT);
    	}
    }
    

    可以看到,请求终结符就是一个#,那就意味着请求中不可以再包含#了,否则会被当做终结符。

            以上就是一个简单的阻塞服务器,可以看到,如果服务器处理的字符串特别长,就有可能影响其后的请求处理,因为代码正执行到toUpperCase,没有返回,导致无法执行accept,从而无法完成TCP三次握手,也就没有办法建立连接。




    展开全文
  • 常见的几种服务器/客服端模型

    千次阅读 2019-07-26 16:32:12
    其实常见的几种服务器/客服端模型都可以,用餐厅,服务员,客人来解释。 餐厅:代表服务器 顾客:代表客服端 服务员:代表处理客人请求的事件 菜单:菜单方便顾客了解菜品和价格,客人读了菜单,然后点菜,相当...

    其实常见的几种服务器/客服端模型都可以,用餐厅,服务员,客人来解释。

    • 餐厅:代表服务器
    • 顾客:代表客服端
    • 服务员:代表处理客人请求的事件
    • 菜单:菜单方便顾客了解菜品和价格,客人读了菜单,然后点菜,相当于服务器发给客服端的数据
    • 记账本:服务员记录顾客点了那些菜,方便后厨做菜和服务员收钱,相当于客服端给服务器发送数据

    大致流程:

    1. 顾客进入餐厅(前提是餐厅门开了且正在营业∩﹏∩),
    2. 服务员就前来招待,
    3. 顾客会先看一下菜单,然后点餐,服务员在记账本记下菜品然后交给后厨做菜(现实中顾客对服务员的请求不光是点菜,有时候可能会问厕所在哪∩︿∩)
    4. 菜做好了,服务员把菜端上来,然后顾客开吃。
    5. 顾客吃完了,招呼一声服务员结账,顾客结完账没什么事就离开餐厅。

    下面就以具体的模型分情况介绍。

    ======================================================================================

    单线程循环模型

    ======================================================================================

    这个餐厅只有一个服务员,这个服务要处理所有顾客的点菜,结账等请求,如果当前顾客的请求事件一直没有处理完,其他需要处理请求的顾客就会一直等待,有些顾客不愿等,就会到别家餐厅吃饭。

    简单代码:

    listen(serverfd, listen_num);
    while(1)
    {
    	connfd = accept(serverfd);
    	read(connfd);
    	write(connfd);
    	close(connfd);
    }
    • 优点:由于只请了一个服务员,所以节省了人工费
    • 缺点:当顾客少时,服务员能即使处理请求,当顾客多时,服务员就忙不过来了,也会损失一部分顾客。

    实际的服务器/客服端

    优点:

    • 简单、易于实现
    • 没有同步、加锁这些麻烦事,也没有这些开销

    缺点:

    • 不能同时处理过多的客服端请求
    • 没有利用多核cpu的优势

     

    餐厅老板发现,顾客一多,服务员就忙不过来,干脆就每来一个顾客,就给他分配一个服务员,这个服务员就专门处理一个顾客的请求。

    然后就变成下面的情况:

    一个人来就餐,一个服务员去服务,然后客人会看菜单,点菜。 服务员将菜单给后厨。

    二个人来就餐,二个服务员去服务……

    五个人来就餐,五个服务员去服务……

    而这种情况有分多进程和多线程

    ======================================================================================

    多进程模型

    ======================================================================================

    由于每个菜单和记账本都属于餐厅的资源(服务器的资源),而多进程就是给每一个服务员都分配一个菜单和记账本,服务员之间各用各的,没有竞争关系。

    简单代码:

    listen(serverfd, listen_num);
    while(1)
    {
    	connfd = accept(serverfd);
    	if ((child_pid = frok()) == 0) {
    		close(serverfd);
    		read(connfd);
    		write(connfd);
    		close(connfd);		
    	}
    	close(connfd);
    }

    优点:可以同时满足多个顾客的请求。

    缺点:成本高(每个服务员都有菜单和记账本),一个服务员还好,如果一百个服务员,那餐厅直接亏本。

    ======================================================================================

    多线程模型

    ======================================================================================

    老板发现给每个服务员都分配一个菜单和记账本成本太高,于是只准备一个菜单和记账本,如果一个服务员把菜单和记账本拿走了,只能等她把菜单和记账本归还后才能使用(这里相当于互斥锁),这样既可以解决同时服务多个顾客的问题,又可以不让成本太高。

    简单代码:

    listen(serverfd, listen_num);
    while(1)
    {
    	connfd = accept(serverfd);
    	pthread_create(&tid, NULL, &doit,  (void *)connfd);
    }
    void *doit(void *arg)
    {
    	pthread_detach(pthread_self());
    	read(connfd);
    	write(connfd);
    	close(connfd);
    }
    • 优点:可以解决同时服务多个顾客的问题,又可以不让成本太高。
    • 缺点:成本还是很高,而且还进行互斥,增加了管理的麻烦。

    具体代码可参考我的博客:https://blog.csdn.net/qq_40732350/article/details/89043038

    ======================================================================================

    进程池和线程池模型

    ======================================================================================

    老板又发现,多线程和多进程分配的服务员太多了,成本太高了,而且每次都是顾客进店后才让一个服务员准备
    服务员准备的过程:穿上工作服,拿好菜单和记账本等等。
    这样再时间上太慢,不能及时的服务顾客。

    于是老板在开张时一次性安排10服务员,这10个服务员把所有准备工作都好然后迎接顾客,如果顾客太多,10个服务员不够,那就要靠餐厅的协调能力,而且得让顾客等一会儿。

    代码就不写了,参考下面的图

    • 优点:成本低,响应时间快
    • 缺点:成本比单线程高,需要系统来协调。

    具体代码可参考我的博客:https://blog.csdn.net/qq_36221862/article/details/73694504

    ======================================================================================

    I/O多路复用模型

    ======================================================================================

    老板又发现(这个老板老是发现∩▂∩)多线程和多进程的成本都很高,而且有的顾客有特别磨叽,半天也没点好菜,耽误了太多时间。

    于是就用下面的办法。

    餐厅只分配一个服务员,在顾客点菜时,服务员把菜单放在桌子上,马上离开去服务其他顾客,等顾客点完了通知一下服务员,然后服务员再来处理。

    由于Linux中多了复用有两种方式:select 和 epoll

    select

    while(1)
    {
    	select(fd_max+1, &cpy_reads, 0, 0, &timeout))== -1)
    	for(i=0; i<fd_max+1; i++) {
    		if(FD_ISSET(i, &cpy_reads)) {
    			if(i==serv_sock) {    //有一个新顾客来了
    				adr_sz=sizeof(clnt_adr);
    				clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
    				FD_SET(clnt_sock, &reads);
    				if(fd_max<clnt_sock) fd_max=clnt_sock;
    			}else{               //顾客发送请求了
    				str_len=read(i, buf, BUF_SIZE);
    				if(str_len==0) { //顾客走了
    					FD_CLR(i, &reads);
    					close(i);
    				}else{
    					write(i , buf, str_len); //给顾客反馈
    				}
    			}
    		}
    	}
    }

    epoll

    while(1)
    {
    	event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
    	for(i=0; i<event_cnt; i++ ) {
    		if(ep_events[i].data.fd==serv_sock) {    //有一个新顾客来了
    			dr_sz=sizeof(clnt_adr);
    			clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
    			event.events=EPOLLIN;
    			event.data.fd=clnt_sock;
    			epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
    		}else{
    			str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);//顾客发送请求了
    			if (str_len==0) { //顾客走了
    				epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
    				close(ep_events[i].data.fd);
    			}else{
    				write(ep_events[i].data.fd, buf, str_len); //给顾客反馈			
    			}
    		}
    	}
    }

    epoll又分  边缘触发 和水平触发

    • 水平触发方式:如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知。允许在任意时候重复检测IO的状态,没有必要每次描述符就绪后尽可能多的执行IO,select,poll就属于水平触发事件。只要满足要求就触发一个事件。
    • 边缘触发方式:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知。在收到一个IO事件通知尽可能多的执行IO操作,因为如果再一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取就绪的描述符。信号驱动式IO就属于边缘触发。每当状态改变就触发一个事件。
    • 优点:成本低,不用加互斥条件,可以最大限度的利用CPU
    • 缺点:不能同时服务多个请求

    可以参考我的博客:https://blog.csdn.net/QQ2558030393/article/details/90896317

     

    小结:每种模型都有它使用的场景,适合的才是最好的。

     

    参考:

    http://server.51cto.com/sOS-589117.htm

    https://cloud.tencent.com/developer/article/1376352

    https://blog.csdn.net/yexiangCSDN/article/details/85988776

    https://blog.csdn.net/qq_36221862/article/details/73694504

    https://blog.csdn.net/QQ2558030393/article/details/91360750

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 客户/服务器模型

    千次阅读 2018-07-25 15:03:10
    今天我们主要来通过一个最基本的回射服务器和客户端模型的编写,来体会到客户和服务器的角色定位,以及对套接字更深的理解。 首先要一些准备知识,了解在Linux下的网络编程的套接字接口。 socket函数 功能:...
  • 常见多线程并发服务器编程模型

    千次阅读 2013-12-26 20:52:18
    2、每个进程都自己的文件描述符(包括file fd, socket fd, timer fd, event fd, signal fd),一般是1024,可以通过ulimit -n 设置,但所有进程打开的文件描述符总数上限,跟主机的内存有关。 3、一个进
  • 服务器模型分析与验证

    千次阅读 2012-03-06 15:28:05
     对于常见的几种服务器模型过服务器端开发经验或研究了解过这个领域的可能都清楚。类似介绍这些模型,分析各自特点优劣的文章不胜枚举,本来在这个方向班门弄斧的挥上一笔似乎已经没有太大价值。但是,正由于...
  • 当前业界常见服务器性能指标:  TPC-C  TPC-E  TPC-H  SPECjbb2005  SPECjEnterprise2010  SPECint2006 及 SPECint_rate_2006  SPECfp2006 及 SPECfp_rate_2006  SAP SD 2-Tier  LINPACK  
  • C/S模型即客户(client)/服务器(server)模型。 1.特点 服务器提供服务,客户请求服务。 2.客户端和服务器之间连接的数量对应关系 多个客户进程可以同时访问一个服务进程,一个客户进程可以同时访问多个服务器进程...
  • 常用的服务器模型总结

    千次阅读 2016-03-28 21:29:20
    I/O模型同步I/O 阻塞I/O,I/O复用,和信号驱动I/O 都是同步I/O模型. 这种I/O的读写操作,都是在I/O事件发生之后,由应用程序来完成. 同步I/O模型要求用户代码自行执行I/O操作,将数据从内核缓冲区读入用户缓冲区,或...
  • 多线程服务器模型

    千次阅读 2013-04-10 16:20:19
    总结了一两种常用的线程模型,归纳了进程间通讯与线程同步的最佳实践,以期用简单规范的方式开发多线程程序。 文中的“多线程服务器”是指运行在 Linux 操作系统上的独占式网络应用程序。硬件平台为 Intel x64 ...
  • 物联网开发 5 UDP并发服务器模型

    万次阅读 2015-08-05 11:20:34
    第一种是最为常见的,TFTP传输的方式。 第二种是笔者无聊时自己编写,功能难免会比较简单、也会许多不足。兴趣的同学可以一起讨论,把功能做的更强大一些。 现将思路整理如下,兴趣的同学可以一起讨论。...
  • 服务器启动,监听特定的端口(如80,21),如端口已被其他应用占用,则启动失败。 2. 客户端启动,打开一个自由端口,主动发起连接请求,经过”三次握手“,连接正式建立。 3. 服务器创建新的服务线程,服务线程打开...
  • Linux服务器网络开发模型

    千次阅读 2014-03-26 20:19:48
    linux 服务器 io 模型
  • 后台服务器设计模型总结

    千次阅读 2014-04-19 21:35:04
    l 单进程同步模型: 服务器只有在处理完成前一个客户请求才能处理下一个请求,最简单的服务器设计模型,没有之一,性能基本取决于后台操作的耗时。 l 多进程同步(fork)模型:每来一个请求fork一个进程处理,...
  • 常见的线程模型

    千次阅读 2018-10-15 18:47:46
    常见的线程模型 对于服务器应用而言,无论是web应用服务还是DB服务,高并发请求始终是一个绕不开的话题当大量请求并发访问时,一定伴随着资源的不断创建和释放,导致资源利用率低,降低了服务质量。 1、单线程...
  • 常见的IO模型

    千次阅读 2018-07-12 15:20:34
    一、服务器端编程经常需要构造高性能的IO模型常见的IO模型有四种: 1.同步阻塞IO(BlockingIO):即传统的IO模型。 2.同步非阻塞IO(Non-blockingIO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为...
  • web 服务器有哪些

    万次阅读 多人点赞 2018-08-27 16:53:49
    什么是web服务器 "网络服务"(Web Service)的本质,就是通过网络调用其他网站的资源。 Web Service架构和云 如果一个软件的主要部分采用了"网络服务",即它把存储或计算环节"外包"...
  • 每来一个连接,服务器就会新建一个线程来处理该连接的读写事件。 特点: 1)采用阻塞式 I/O 模型读取数据; 2)每个连接都需要独立的线程完成数据读取,业务处理,数据返回的完整操作。 存在问题: 1)当并发数较大...
  • 前言的前言 服务器模型涉及到线程模式和IO模式,搞清楚这些就能针对各种场景有的放矢。该系列分成三部分: * ...从不同维度可以不同的分类,这里从I/O的阻塞与非阻塞、I/O处理的单线程与多线程角度探讨服务器模型
  • 两种高效的服务器设计模型:Reactor和Proactor模型

    万次阅读 多人点赞 2015-05-30 18:25:14
    在文章《unix网络编程》(12)五种I/O模型中提到了五种I/O模型,其中前四种:阻塞模型、非阻塞模型、信号驱动模型、I/O复用模型都是同步模型;还有一种是异步模型。 Reactor模型  Reactor模式是处理并发I/O比较...
  • 客户端—服务器通信模型浅析

    千次阅读 2013-12-07 15:34:53
    客户端—服务器(Client-Server)是我们平时最常见的通信方式,本文就对这一通信模型做个简单介绍。 (一)TCP连接方式:短连接方式?长连接方式 目前,多数的客户端—服务器选择TCP做为传输层协议,也少数选择UDP...
  • 点击进入_更多_Java千百问1、jvm常见配置都有哪些了解jvm内存模型看这里:java内存模型是什么样的 了解jvm内存管理看这里:jvm是如何管理内存的 了解jvm垃圾回收机制看这里:java垃圾回收机制是什么jvm配置非常多...
  • 几种网络服务器模型的介绍与比较

    千次阅读 2010-10-15 13:56:00
    原文的大标题叫“使用事件驱动模型实现高效稳定的网络服务器程序”   -------------------------------------------------------------- 华丽的分割线 -------------------------------------------------------...
  • 围绕如何构建一个高效稳定的网络服务器程序,本文从一个最简单的服务器模型开始,依次介绍了使用多线程的服务器模型、使用非阻塞接口的服务器模型、利用select()接口实现的基于事件驱动的服务器模型,和使用libev...
  • 5种常见的并发模型

    千次阅读 2020-02-11 21:11:19
    5种常见的并发模型 前言 并发在现在已经是十分常见的问题了,由于人类信息量的增加,很多信息都需要并发处理,原有的串行处理已经很难满足现实的需求。 今天我们来讲一讲5种常见的并发模型 1、Future模型 Future模型...
  • 5种常用的服务器编程模型

    千次阅读 2016-11-06 22:54:38
    同步阻塞迭代模型是最简单的一种IO模型。 其核心代码如下: 12345678bind(srvfd);listen(srvfd);for(;;){ clifd = accept(srvfd,...); //开始接受客户端来的连接 read(clifd,buf,...);

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 204,054
精华内容 81,621
关键字:

常见的服务器模型有哪些