文章目录
套接字类型
1 )SOCK_STREAM TCP
2 )SOCK_DGRAM UDP
3 )SOCK_RAW 原始套接字。允许对低于传输层的协议或物理网络直接访问,例如可以接收和发送ICMP包,常用于检测新的协议。
套接字地址结构
ivp4套接字地址结构通常称为“网际套接字地址结构”,名字为sockaddr_in,定义在头文件 <netinet/in.h> 中,结构定义如下:
typedef uint32_t in_addr_t;
typedef uint16_t in_port_t;
typedef unsigned short sa_family_t;
struct in_addr{
in_addr s_addr;
};
struct sockaddr_in{
unit8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
sockaddr_in结构体包含5个成员
sin_addr成员的类型是结构体,存储IP地址。各成员作用如下:
- sin_len 存储套接字地质结构的长度,一般不设置它和检查它,除非涉及到路由套接字。
- sin_family Internet地址族,IPv4是AF_INET
- sin_port 端口号
- sin_addr IP地址
- sin_zero 暂时没有被使用,但总是将它置为0
通用套接字地址结构
套接字地址结构作为参数传递给任一个套接字函数时,通常通过指针传递。当套接字函数取得此参数时,参数中可能存放来自所支持的任何协议族的地址结构。因此套用套接字函数时,需要将指向特定协议的地址结构的指针类型转换为通用地址结构的指针。同文件为<sys/socket.h>
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
}
套接字基本函数
小端字节序:低序字节存储在起始地址(符合人的思维的字节序)
大端字节序:高序字节存储在起始地址(网络字节顺序采用)
主机所用的字节序成为主机字节序,TCP/IP协议规定了网络字节序,主机或路由器发送IP数据包之前要将相应的信息转换为网络字节序,接收到数据包要将数据转换为主机字节序。
转换函数:
<netinet/in.h>
- htons host to net short(16位)
- htonl
- htohs
- ntohl
字节操作函数
<string.h>
5. memcpy(void *dest,void src,size_t len)
-
bzero(void *dest, size_t nbytes)函数将目标中指定数目的字节置为0,经常用来对套接字地址结构进行初始化;
-
copy(const void *src, void *dest, size_t nbytes)函数将指定数目的字节从源拷贝到目标;
-
bcmp(const void *src, void *dest, size_t nbytes);函数比较源和目标两字字符串,相同返回值为0,否则返回非0值。
IP地址转换函数
<arpa/inet.h>
- int inet_aton(const char *cp, struct in_addr *inp)- 将字符串形式的IP地址转化为32位二进制的IP地址(网络字节序)
- in_addr_t inet_addr(const char *cp) - in_addr_t 一般为 32位的unsigned int,其字节顺序为网络顺序
- char *inet_ntoa(struct in_addr inaddr)-32位网络字节序二进制转换为点分十进制
TCP套接字编程
socket()函数
<sys/socket.h>
socket(int family,int type,int protocol)
family
- AF_INET
- AF_INET6
- AF_ROUTER
type
- SOCK_STREAM -TCP
- SOCK_DGRAM-UDP
- SOCK_RAW-原始套接字
protocol
一般为0,原始套接字为其它值
connect()函数
<sys/socket.h>
int connect(sockfd,(struct sockaddr *)&address,sizeof(service))
用于建立TCP的三次握手过程,建立与远程服务器的连接
第一个参数sockfd是由socket()函数返回的套接字描述符,
第二个参数是指向服务器的套接字地址结构的指针
第三个参数是该套接字地址结构的长度
调用成功返回0,出错返回-1
#include "sys/socket.h"
void main()
{
int sockfd;
struct sockaddr_in server;
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(1234);
server.sin_addr.s_addr=inet_addr("127.0.0.1");
connetc(sockfd,(struct sockaddr *)&server,sizeof(server));
}
bind()函数
<sys/socket.h>
bind(int sockfd,const struct sockaddr *server,sock_len addrlen)
为调用socket()函数产生的套接字分配一个本地协议地址,建立地址与套接字的对应关系。对于网际协议,协议地段包括32位的IPV4地址或者128位的IPV6地址和16位的UDP或TCP端口号。
sockfd是套接字函数返回的套接字地址描述符
server是指向特定协议地址的地址结构的指针,指定用于通信的本地协议地址
addrlen指定了套接字地址结构的长度
调用成功返回0,出错返回-1,如果不绑定任何端口,内核会为套接字选择一个临时的端口
#include "sys/socket.h"
void main()
{
int sockfd;
struct sockaddr_in server;
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(1234);
server.sin_addr.s_addr=inet_addr("127.0.0.1");
bind(sockfd,(struct sockaddr *)&server,sizeof(server));
}
linsten()函数
<sys/socket.h>
int listen(int sockfd,int backlog)
sockfd是套接字函数返回的套接字地址描述符
backlog参数规定了请求队列的最大连接数
维护两个队列
- 未完成连接队列,为每个请求建立连接的SYN分节开设一个条目,服务器正等待完成TCP三次握手,当前的套接字处于SYN_RCVD状态
- 已完成连接队列:为每个完成TCP三次握手的客户端开设一个条目,当前的套接字状态为ESTABLISHED
调用成功返回0,出错返回-1
#include "sys/socket.h"
void main()
{
int sockfd;
int BACKLOG=5;
if((listen(sockfd,BACKLOG))==-1)
{
error;
}
}
accept()函数
<sys/socket.h>
accept()函数使服务器接受客户端的连接请求。它将完成队列中的队头条目返回给进程,并产生一个新的套接字描述符,这个新生成的描述符为“已连接套接字”,当已完成队列为空时,进程睡眠,直到有已完成的连接到达时。
accpet(int listenfd,struct sockaddr *client,socklen_t *addrlen)
listenfd参数是由socket()函数产生的套接字描述符,在调用accept()函数前,已经调用listen()函数将此套接字变成了监听套接字;
client和addrlen用来返回连接对方的套接字地址结构和对应的结构长度,addrlen是一个结果参数,调用前,将addrlen指针所指的整数值置为client所指的套接字地址结构的长度。
#include "sys/socket.h"
void main()
{
int listenfd,connfd;
struct sockaddr_in client;
socklen_t addrlen;
addrlen=sizeof(client);
…
connfd=accept(listenfd,(strlen sockaddr *)&client,&addrlen);
}
write()和read()
int write(int sockfd,char *buf,int len)
sockfd是套接字描述符
buf是发送信息缓冲区
len是传送缓冲区大小
int read(int sockfd,char *buf,int len)
int write(int sockfd,char *buf,int len)
sockfd是套接字描述符
buf是发送信息缓冲区
len是接收缓冲区大小
sockaddr、sockaddr_in、in_addr
sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
struct sockaddr_in{
unit8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
IP地址结构
struct in_addr{
in_addr s_addr;
};
UDP套接字编程
客户端步骤
- 建立UDP套接字;
- 发送信息给服务器;
- 接收来自服务器的信息;
- 关闭套接字
sendto()函数
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen);
返回:大于0-成功发送数据长度;-1-出错;
flags是传输控制标志,其值定义如下:
0:常规操作,如同write()函数;
MSG_OOB:发送带外数据;
MSG_DONTROUTE:忽略底层路由协议,直接发送。
recvfrom()函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);
返回:大于0-成功接收数据长度;-1-出错;
UDP套接字使用无连接协议,因此必须使用recvfrom函数,指明源地址;
flags是传输控制标志,其值定义如下:
0:常规操作,如同read()函数;
MSG_PEEK:只察看数据而不读出数据;
MSG_OOB:忽略常规数据,而只读取带外数据
UDP服务器
#include "stdio.h"
#include "string.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "stdlib.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#define PORT 1234
#define MAXDATASIZE 100
void main(){
int sockfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t len;
int num;
char buf[MAXDATASIZE];
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1){
perror("Create socket failed.");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&server,sizeof(server))==-1){
perror("Bind() error");
exit(1);
}
len=sizeof(client);
while(1){
num=recvfrom(sockfd,buf,MAXDATASIZE,0,(struct sockaddr *)&client,&len);
if(num<0){
perror("recvfrom() error");
exit(1);
}
buf[num]='\0';
printf("Your got a message <%s> from cient.\n It's ip is %s,port is %d.\n",buf,inet_ntoa(client.sin_addr),htons(client.sin_port));
sendto(sockfd,"Welcome\n",8,0,(struct sockaddr *)&client,len);
if(!strcmp(buf,"bye"))
break;
}
}
UDP客户机
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "string.h"
#include "sys/socket.h"
#include "sys/types.h"
#include "netinet/in.h"
#include "netdb.h"
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc,char *argv[]){
int sockfd,num;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in server,peer;
if (argc !=3)
{
printf("Usage:%s <IP Address> <message>\n",argv[0]);
exit(1);
}
if ((he=gethostbyname(argv[1]))==NULL)
{
printf("gethostbyname() error\n");
exit(1);
}
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
printf("socket() error\n");
exit(1);
}
bzero(&server,sizeof(server));
server.sin_family= AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
sendto(sockfd, argv[2], strlen(argv[2]),0,(struct sockaddr *)&server,sizeof(server));
socklen_t len;
len=sizeof(server);
while(1)
{
if ((num=recvfrom(sockfd,buf,MAXDATASIZE,0,(struct sockaddr *)&peer,&len)) == -1)
{
printf("recvfrom() error\n");
exit(1);
}
if (len != sizeof(server) || memcmp((const void *)&server,(const void *)&peer,len) != 0)
{
printf("Receive message from other server.\n");
continue;
}
buf[num-1]='\0';
printf("Server Message:%s.\n",buf);
break;
}
close(sockfd);
}
并发服务器
服务器按照处理方式可以分为迭代服务器和并发服务器。
迭代服务器
并发服务器
fork()与vfock()的区别
fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别:
- fork ():子进程拷贝父进程的数据段,代码段
vfork ( ):子进程与父进程共享数据段 - fork ()父子进程的执行次序不确定
vfork 保证子进程先运行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec
或exit 之后父进程才可能被调度运行。 - vfork ()保证子进程先运行,在它调用exec 或exit 之后父进程才可能被
#include "unistd.h"
void main()
{
pid_t pid;
if((pid=fork())>0){
//父进程
}
else if(pid==0){
//子进程
}
else{
//出错
}
}
域名系统
- A记录将一个域名地址对应一个ipv4地址,如NET IN A 202.121.68.5,NET是主机名称,是使用的一台主机;IN表示属于某种类型的记录
- AAAAA记录将一个域名地址对应一个ipv6地址
- NS记录用于指定一台主机的域名,它负责定义由哪个域名服务器负责管理维护本区域的记录,NET IN NS server.cuit.edu.cn
- MX记录用于指定一台主机的域名,所有发送到本域的电子邮件由这台主机接收
- PTR记录将IP地址映射为主机名
- CNAME作用是允许主机建立别名,如果人们使用这些别名而不是实际的主机名,则它在服务器挪到其他主机上时是透明的
多播和广播
广播的用途
- 在本地子网定位一个服务器主机,前提是已知或认定这个服务器主机位于本地子网,但是不知道它的单播IP地址。这种操作也称为资源发现(resource discovery)。
- 当有多个客户和单个服务器通信时,减少局域网上的数据流量。
DOS攻击
根据TCP/IP协议,正常的ACK应会送给客户机,但由于客户机在发送SYN时修改了源地址,服务器收到这样的数据报时,就将ACK发送给一个目标地址不存在的主机,并且等待目标主机发送过来的ACK应答,直到超时。而TCP协议栈未连接队列的长度是有限的,当队列中有许多这样的连接时,正常的连接无法处理,从而拒绝服务。