网络编程(1)

一、主机字节序和网络字节序

主机字节序也称小端字节序:整数的高位字节存储在内存的高地址处,低位字节存储在内存的低地址处

网络字节序也称大端字节序:整数的高位字节存储在内存的底地址处,低位字节存储在内存的高地址处

二、检查机器的字节序

小字节序(Little Endian)
DATA:0x0201
总结:低地址存放低位数据

大字节序(Big Endian)
DATA:0x0201
总结:低地址存放高位数据

字节序的支持跟CPU的设计有关,不同架构的CPU可能具有不同的字节序,常见的intel芯片,大多是小字节序

而PowerPC架构多是大字节序的芯片。

测试机器字节序代码:

#include <stdio.h>
union 
{
    short value1;
    char  value2[sizeof(short)];
}text;

int main()
{
    text.value1 = 0x0102;   
    if(text.value2[0] == 1 && text.value2[1] == 2)
    {
        printf("big ending\n");                     //大端字节
    }
    else if(text.value2[0] == 2 && text.value2[1] == 1)
    {
        printf("short ending\n");                   //小端字节
    }
    else
    {
        printf("unknown ...");
    }
    return 0;
}

执行结果

三、Linux字节序转化函数

htonl   (长整型32位)   主机字节序转网络字节序

ntohl (长整型32位)网络字节序转主机字节序

htons  (短整型16位)   主机字节序转网络字节序

ntohs(短整型16位)网络字节序转主机字节序

#include <stdio.h>
#include <arpa/inet.h>
int main()
{
    short A=88;
    int   B=65536;
    short C;
    int D;
    C=htons(A);
    printf("%d htons = %d\n",A,htons(A));
    printf("%d ntohs = %d\n",C,ntohs(C));
    D=htonl(B);
    printf("%d htonl = %d\n",B,htonl(B));
    printf("%d ntohl = %d\n",D,ntohl(D));
    return 0;
}

四、IP地址转换

通常我们使用点分十进制来表示IP地址,但编程中我们需要把它们转化为整数才能使用,而记录日志时则相反,

我们需要把整数表示的IP地址转化为点分十进制。

头文件

       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
  

/* 用于将点分十进制表示的IPV4地址转化为网络字节序整数表示的地址*/

in_addr_t  inet_addr(const char *str);

/*用于转换整数表示的网络字节排序地址为点分十进制的字符串。该字符串的空间为静态分配的,这意味着在第二

次调用该函数时,上一次调用时将会被重写(复盖)*/

char  *inet_ntoa(struct in_addr_in);

EPE:

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IP1 "192.168.47.128"
#define IP2 "192.168.1.1"
int main()
{
    struct in_addr A;
    A.s_addr = 65535;
    printf("IP1 is %s   change to IP1:=%d\n",IP1,inet_addr((const char *)IP2));
    printf("IP2 is %s      change to IP2:=%d\n",IP2,inet_addr((const char *)IP1));
    printf("A.s_addr is %d       change to IP3:=%s\n",A.s_addr,inet_ntoa(A));
    A.s_addr = 16885952;
    printf("A.s_addr is %d    change to IP4:=%s\n",A.s_addr,inet_ntoa(A));
    return 0;
}

REL:

五、Linux socket(套接字)

套接字:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口。

Linux中的网络编程通过Socket(套接字)接口实现。

Socket是一种文件描述符,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

1.流式套接字(SOCK_STREAM)          

定义一种可以提供可靠的、面向连    接的通讯流,它使用了TCP协议。

2.数据报套接字(SOCK_DGRAM)  

定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的不保证可靠,无差错,UDP协议 。

3.原始套接字  

原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议的测试等。

TCP/IP专用socket结构体

sa_family_t 地址族类型地址族类型通常与协议族类型相对应,AF_INET表示TCP/IPv4协议族

PF_INET6 表示TCP/IPv6协议族。

 

SOCKET——实现步骤

1.创建socket——socket()

2.绑定socket——bind()

首先将整个结构体清零,使用bzero()或memset

 ***函数格式 void *memset(void *s, int ch, size_t n)
 函数解释:将s中前n个字节替换为ch并返回s;是用来在在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清   零操作的一种最快方法。


***函数格式void bzero(void *s, int n);

函数解释:s要置零的数据的起始地址

                 n要置零的数据字节个数,用来置字节字符串s的前n个字节为零且包括‘\0’且无返回值。

3.监听socket——listen()

函数原型: int listen(int sockfd, int backlog);

listen函数的第一个参数即为要监听的socket描述字(套接字)

第二个参数为相应socket可以排队的最大连接个数。

socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

4.接受socket——accept()

5.发起连接——connect()

6.关闭连接——close()

7.socket TCP数据读写——recv()、send()、recvfrom() 、sendto()

Socket的send函数的执行流程。当调用该函数时,

(1)send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR; 
(2)如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议s的发送缓冲中的数据是否正在发送,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len 
(3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完 
(4)如果len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。

如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

Socket的recv函数的执行流程。当应用程序调用recv函数时,

(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,

(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

其他的形式

•read()/write() 
•readv()/writev() 
•recvmsg()/sendmsg() 
 

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int  flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

read函数是负责从fd中读取内容。当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

write函数

将buf中的nbytes字节内容写入文件描述符fd。成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有两种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。

recv函数和send函数提供了read和write函数一样的功能,不同的是他们提供了四个参数。前面的三个参数和read、write函数是一样的。第四个参数可以是0或者是以下组合: 
MSG_DONTROUTE:不查找表,是send函数使用的标志,这个标志告诉IP,目的主机在本地网络上,没有必要查找表,这个标志一般用在网络诊断和路由程序里面。 
MSG_OOB:表示可以接收和发送带外数据。 
MSG_PEEK:查看数据,并不从系统缓冲区移走数据。是recv函数使用的标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样在下次读取的时候,依然是一样的内容,一般在有个进程读写数据的时候使用这个标志。 
MSG_WAITALL:等待所有数据,是recv函数的使用标志,表示等到所有的信息到达时才返回,使用这个标志的时候,recv返回一直阻塞,直到指定的条件满足时,或者是发生了错误。

8.select()函数

connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。

函数声明:

int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout) 

 Maxfd: 文件描述符的范围,比待检的最大文件描述符大1

 Readfds:被读监控的文件描述符集  

指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这文  件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

Writefds:被写监控的文件描述符集  

指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 

Exceptfds:被异常监控的文件描述符集

Timeout:定时器

select的超时时间,它可以使得select进入三种工作状态

一、Timeout取不同的值,该调用有不同的表现:        

1.Timeout值为0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值。      

2. Timeout为NULL,select将阻塞进程,直到某个文件满足要求      

3. Timeout值为正整数,就是等待的最长时间,即select在timeout时间内阻塞进程

二、Select调用返回时,返回值有如下情况:

1. 正常情况下返回满足要求的文件描述符个数;

2. 经过了timeout等待后仍无文件满足要求,返回值为0;

3. 如果select被某个信号中断,它将返回-1并设置errno为EINTR。

4. 如果出错,返回-1并设置相应的errno

三、系统提供了4个宏对描述符集进行操作:    

#include <sys/select.h>  

void FD_SET(int fd, fd_set *fdset)   宏FD_SET将文件描述符fd添加到文件描述符集fdset中

void FD_CLR(int fd, fd_set *fdset)      宏FD_CLR从文件描述符集fdset中清除文件描述符fd;

void FD_ZERO(fd_set *fdset)    宏FD_ZERO清空文件描述符集fdset;        

void FD_ISSET(int fd, fd_set *fdset)    在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化

七、

 

 

八、UDP通讯模型

九、服务器类型

在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求, 对服务端的程序就提出了特殊的要求。目前最常用的服务器模型有:    

循环服务器:服务器在同一个时刻只可以响应一个客户端的请求    

并发服务器:服务器在同一个时刻可以响应多个客户端的请求

TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况。

但同时也带来了问题:为了响应客户的请求,服务器要创建子进程来处理,而创建子进程是一种非常消耗资源的操作

1.TCP循环服务器

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

socket(...);
bind(...);
listen(...);
     while(1)
     {
	    accept(...);
	    process(...);
            close(...);
     }

 

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

2.UDP循环服务器

UDP循环服务器的实现方法:UDP服务器每次从套接字上读取一个客户端的请求->处理->然后将结果返回给客户机

                socket(...);
		bind(...);
		while(1)
	       {
		       recvfrom(...);
		       process(...);
		       sendto(...);
	       }

因为UDP是非面向连接的,没有一个客户端可以老是占住服务端, 服务器对于每一个客户机的请求总是能够满足

3.TCP并发服务器

并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个 子进程来处理。算法如下:

                                  socket(...);
                                  bind(...);
		                  listen(...);
       		       while(1) {
	                                  accept(...);
			            if(fork(..)==0) {
	   		              process(...);
	   			 close(...);
        			  exit(...);
				}
				close(...);
			}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值