socket编程模式:
1.定义参数,sockaddr_in ,sockaddr是系统提供的描述socket信息的结构体,需要用户来赋初值.
sockaddr_in和sockaddr均是保存socket信息的类型,但sockaddr_in结构更为方便,sin_zero(填充0 以保持与struct sockaddr同样大小以便互相转换使用bzero() 或 memset() 来全部置零.)
上述结构体如sockaddr_in ,sockaddr均已在系统头文件sys/socket.h中定义好了.
2.给上述结构体实例赋值
3.具体过程:
需要用到的函数介绍:
inet_addr("132.241.5.10"):将IP地址从点数格式转换成无符号长整型.
bzero(&(my_addr.sin_zero)) 或 memset(&(my_addr.sin_zero)):将struct sockaddr_in中的sin_zero置零.
htons(): "Host to Network Short"将本机字节顺序转换成网络字节顺序.
fcntl(sockfd, F_SETFL, O_NONBLOCK),设置阻塞.
gethostbyname(“www.google.com”)得到远地主机的地址信息.
select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout):多路同步函数调用,参数int numfds为同步运行的socket最大数目,fd_set *readfds为指向存储多路同步读socket描述符集合的指针.同理fd_set *writefds为指向存储多路同步写socket描述符集合的指针.在调用select()之前必须调用宏FD_SET(int fd, fd_set *set)描述符添加到集合中.
面向相连接服务端:
必须得到本机ip,本机socket描述符,本机端口号.
a.socket()建立socket指名使用协议,类型(数据流还是数据报).返回值:int(socket描述符)
b.bind()绑定本机端口号,本机iP地址.返回值:int
c.listen()监听是否有客户端接入,设置连接等待上限.返回值:int
d.accept()同意接受客户端的接入,同时存贮接入的客户端的struct sockaddr_in型的结构体(包含客户端所要连接的端口号,客户端的ip地址等).该函数返回另一个socket描述符,即new_fd=accept()为新的socket描述符.用于send()recv()传输数据,原来的socket描述符仍旧用来监听客户端的连接.返回值:int(新的socket描述符)
e.recv()其中*buf是要读取信息的缓冲的指针,即对方发来消息的暂存区,使用新的socket描述符.
f.send()其中*msg是要发送信息的缓冲的指针,使用新的socket描述符.
g.close().
上述返回值主要起着判断错误的作用
面向连接客户端:
必须有服务端ip或网址,服务端连接的端口号,本机socket描述符.
a.socket()建立socket指名使用协议,类型.返回值:int
b.bind()可有可无.
c.gethostbyname()得到服务端的ip地址(该函数是一个域名解析函数,输入服务端的可记忆地址如:http://www.xxxx.com即可返回该服务端的信息).返回值:hostent类型的结构体指针.该结构体实例存贮了返回的服务端的主机名,ip地址(在h_addr字段).
d.connect()与服务端连接,该连接指定本机的socket描述符,将要连接的服务端的地址结构及长度.返回值:int.
e.send()
f.recv()
g.close()
综述:
1.socket为两台主机的连接点,每台主机都有一个或多个socket用于连接传输数据(用户自行创建).用户可以根据需要选择socket连接点.任何连接都要建立在socket所建立的管道上.所以函数listen()accept()connect()send()recv()等都要绑定socket描述符.socket描述符可看作是每台机器的socket的id,该id在该机器上是唯一的,因此可用于标示该socket.
2.两台机器使用socket连接传输时需要一个全相关即(协议,本地地址,本地端口号,远地地址,远地端口号).协议在创建socket时由用户指定,本机地址和端口号可由服务程序自行填充和分配或由用户调用bind()自行填充和分配.远地地址和远地端口号同样由远地主机创建,通过connect(),accept(),gethostbyname()传送或取得.
socket(),accept()等函数为阻塞函数,即如果接受不到数据就阻碍整个程序的执行,除非出现异常.利用fcntl()函数可以将socket()设置为非阻塞即
int newsocket;
newsocket = socket(PF_INET, SOCK_STREAM, 0 );
fcntl( newsocket, F_SETEL, O_NONBLOCK );
以后只要绑定newsocket的accept()等函数就不会阻塞.但查询该socket连接信息时会浪费大量资源.比如绑定非阻塞的socket的非阻塞的accept()函数,服务器使用recv()接受信息时会不停的询问accept(),系统也会不停调用accept(),从而浪费大量cpu资源.如果accept()设置为阻塞则当recv()询问accept()时,由于accept()挂起,recv()也挂起,直到accept()接到客户端connect()来的信息为止.
ip头的数据结构:
[StructLayout(LayoutKind.Explicit)]
public struct IPHeader
{
[FieldOffset(0)] public byte ip_verlen; //4位首部长度+4位IP版本号
[FieldOffset(1)] public byte ip_tos; //8位服务类型TOS
[FieldOffset(2)] public ushort ip_totallength; //16位数据包总长度(字节)
[FieldOffset(4)] public ushort ip_id; //16位标识
[FieldOffset(6)] public ushort ip_offset; //3位标志位
[FieldOffset(8)] public byte ip_ttl; //8位生存时间 TTL
[FieldOffset(9)] public byte ip_protocol; //8位协议(TCP, UDP, ICMP, Etc.)
[FieldOffset(10)] public ushort ip_checksum; //16位IP首部校验和
[FieldOffset(12)] public uint ip_srcaddr; //32位源IP地址
[FieldOffset(16)] public uint ip_destaddr; //32位目的IP地址
}
注:StructLayout(LayoutKind.Explicit)类通过设置FieldOffset()字段,可以让用户控制类或结构体的布局.FieldOffset(n)代表该成员离类或结构体头部的长度为n个字节(8位).