socket
1、一个socket里有一个相关描述(协议,本地地址,本地端口,远程地址,远程端口)。
2、(1)创建socket(相当于一个结构体,里面有很多成员,它创建在内核种):#include<sys/socket.h>
int socket(int domain,int type,int protocol);//若创建成功则返回一个文件描述符,出错返回-1
(2)socket的参数
domain:AF_INET IPv4因特网域
AF_INET6 IPv6因特网域
AF_UNIX unix域
AF_UNSPEC 未指定
protocol:
通常为0,默认的协议
type:
SOCK_STREAM:表tcp使用tcp协议进行传输
SOCK_DGRAM:表udp
SOCK_RAW:原始套接字允许对底层协议进行直接访问
SOCK_SEQPACKET:
3、(1)字节序:表示数据的多个字节的高字节和低字节在前还是在后的问题。大断字节:高字节在前,低字节在后,相反的叫小端字节序。
(2)网络协议(即在网络种传输时)使用的是大端字节序。
(3)字节序的转换函数:
htonl():将32位的主机字节序转换为网络字节序
htons():将16位的主机字节序转换为网络字节序
ntohl():将32位的网络字节序转换为主机字节序
ntohl():将32位的网络字节序转换为主机字节序
4、(1)通用地址结构(一般不用)
struct sockaddr{
unsigned short sa_family;//即domain AF_xxx
char sa_data[14];//包含远程地址、端口和套接字的数目等
}
(2)因特网地址结构
struct in_addr{
in_addr_t s_addr;//ipv4地址
}
struct sockaddr_in{
short int sin_family;//AF_xxx(主机字节序)
unsigned short int sin_port;//16位的端口号(网络字节序)
struct in_addr sin_addr;//ipv4地址(网络字节序)
unsigned char sin_zero[8];//格式对齐补充位
}(此结构体更常用)
5、IPV4网络字节序与点分十进制的转换函数
#include<arp/inet.h>
const char *inet_ntop(int domain,const void *restrict addr,char *restrict str,socklen_t size);//将网络字节序转换成点分十进制。成功返回地址字符串指针,出错返回NULL
int inet_pton(int domain,const char *restrict str,void *restrict addr);//点分十进制转换为网络字节序,成功返回1,无效格式返回0,出错返回-1
参数:
domain:AF_INET
addr:32为ipv4网络字节序
str:十进制的地址
size:地址字符串大小
6、例
struct sockaddr_in sin;//定义一个sockaddr_in结构体
char buf[16];
//填充定义的结构体
memset(&sin,0,sizeof(sin));//将定义的结构体清零
sin.sin_family=AF_INET;//填充sin_family字段
sin.sin_port=htons(3001);//填充端口号,并将其变为网络字节序
if(inet_pton(AF_INET,"192.168.2.1",&sin.sin_addr.s_addr)<=0)//填充地址字段,并把10进制的地址转换为网络字节序
{
//错误的
}
printf("%s\n",inet_ntop(AF_INET,&sin.sin_addr.s_addr,buf,sizeof(buf)));//又把填充的网络地址转换为10进制输出出来
7、服务器端和客户端的流程
(1)服务器端:调用socket函数创建套接字,调用bind绑定本地地址和端口,调用listen启动监听,调用accept从已连接队列中提取客户连接,调用I/O函数(read/write)与客户端通讯,调用close关闭套接字
(2)调用socket函数创建套接字,调用connect连接服务器端,调用I/O函数(read/write)与服务器端通信,调用close关闭套接字
8、一个服务器端的程序
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
//用于捕获ctr+c信号,若捕获到则终止程序
void sig_handler(int signo)
{
if(signo=SIGINT)//SIGINT关联ctr+c
{
printf("server close\n");
close(sockfd);//关闭服务器的socket
exit(1);
}
}
//输出客户端的相关信息
void out_addr(struct sockaddr_in *clientaddr)
{
int port=ntohs(clientaddr->sin_port);//将网络字节序转变为主机字节序
char ip[16];
memset(ip,0,sizeof(ip));
inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));//将ip地址从网络字节序转换成点分十进制
printf("client:%s(%d) connected\n",ip,port);
}
void do_service(int fd)
{
long t =time(0);//获得系统时间
char *s=ctime(&t);//转换成字符串的形式
size_t size=strlen(s)*sizeof(char);
if(write(fd,s,size)!=size)//将获得的时间写回客户端中,fd针对某个客户端
{
perror("write error");//失败
}
}
int sockfd;
int main(int argc,char* argv[])
{
if(argc<2)
{
printf("usage:%s #port\n",argv[0]);
exit(1);
}
if(signal(SIGINT,sig_handler)==SIG_ERR)//捕获ctr+c信号
{
perror("signal sigint error");
exit(1);
}
/*
*/
sockfd=socket(AF_INET,SOCK_STREAM,0);//传教一个套接字,选择IPV4,tcp协议,相当于创建了一个在内核中的结构体
//定义地址结构并填充相关信息
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(atoi(argv[1]));//atoi()将输入的字符串型转换为整型,并将其转换为网络字节序,端口号由命令行提供
serveraddr.sin_addr.s_addr=INADDR_ANY;//服务器端的地址,不设置固定的IP地址,所有网卡的IP地址都可以(即可以从所有网络获得数据),这里是主机字节序,要将其转换为网络字节序
if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)//将socket和地址(ip,端口)进行绑定.第二个参数将因特网地址转换为通用地址,bind要求的是通用地址,所有要进行转换
{
perror("bind error");
exit(1);
}
if(listen(sockfd,10)<0)
{
perror("listen error");
exit(1);
}//启动监听(指定port监听)通知系统去接受来自客户端的连接请求,将接受到的客户端的连接请求放置到对应的队列中,第二个参数就是队列的大小即可以放10个来自客户端的请求。
struct sockaddr_in clientaddr;//用来存客户端的地址
socklen_t clientaddr_len=sizeof(clientaddr);
while(1)
{
//从上面的队列中选择一个请求进行连接,若队列中没有客户端连接他会阻塞在accept处,一直获得连接
int fd=accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);//出错返回0,第二个参数可写为NULL,这是获得客户端的地址即长度,返回一个新的socket描述符。(与客户端相关,利用此描述符可以与客户端进行通信,即只要对此描述进行操作就可和客户端以全双工的方式进行通信,相当于I/O流)。
if(fd<0)
{
perror("accept error");
continue;//停止此次,继续从客户端获取
}
out_addr(&clientaddr);//显示客户端地址
do_service(fd);//给客户端提供服务
close(fd);//关闭某个客户端的socket
}
return 0;
}
9、客户端例子
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
int main(int argc,char* arg[])
{
if(argc<3)
{
printf("arg error");
exit(1);
}
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建一个套接字
if(sockfd<0)
{
perror("sockfd error");
exit(1);
}
//创建一个地址并填充
struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(atoi(argv[2]));
inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)//将套接字与地址结构相关联,并请求和服务器相连接
{
perror("connect error");
exit(1);
}
char buffer[1024];
memset(buffer,0,sizeof(buffer));
size_t size;
if((size=read(sockfd,buffer,sizeof(buffer)))<0)//从套接字中读取数据
{
perror("read error");
exit(1);
}
if(write(STDOUT_FILENO,buffer,size)!=size)//将读取的数据打印在屏幕中
{
perror("write error");
exit(1);
}
return 0;
}