网络变成首先要注意IP和端口的转换,现在电脑基本上是主机字节序,存储按照小端方式,而在网络中传输统一使用大端方式,所以网络变成首先要注意字节序的转换。
一个常用的ip转换程序的实现:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define CHIP(ip) (ip&0xff)<<24 |(ip&(0xff<<8))<<8 |(ip&(0xff<<16))>>8|(ip&(0xff<<24))>>24 int main(int argc,char *argv[]) { char buf[100]=""; int ip[4]={0}; int oldip,newip; scanf("%s",buf); sscanf(buf,"%d.%d.%d.%d",&ip[0],&ip[1],&ip[2],&ip[3]); //格式化输入,注意去地址符号 printf("%d %d %d %d\n",ip[0],ip[1],ip[2],ip[3]); oldip=(ip[3]<<24)|(ip[2]<<16)|(ip[1]<<8)|ip[0]; printf("%x\n",oldip); // newip=(ip[0]<<24)|(ip[1]<<16)|(ip[2]<<8)|ip[3]; newip=CHIP(oldip);//在计算机中按照十六进制存储的 printf("%x\n",newip); //printf("%d %d %d %d \n",a[0],a[1],a[2],a[3]); memset(buf,0,100); int i; for(i=0;i<4;i++)//将大端模式的IP转换为十进制 好像有BUG { buf[i]=((unsigned int)(newip&((unsigned int)0xff<<8*i))>>8*i); } fprintf(stdout,"%d.%d.%d.%d\n",buf[3],buf[2],buf[1],buf[0]); return 0; }
在网络传输 规定使用大端模式发送,小端模式转大端模式 可以使用这种宏定义
例如小端模式下十六进制的IP为 64 01 a8 c0 //192.168.1.100
#define CHIP(ip) \
(ip&0xff)<<24 | \
(ip&(0xff<<8))<<8 | \
(ip&(0xff<<16))>>8 | \
(ip&(0xff<<24))>>24
转化后的大端模式为:c0 a8 01 64
套接口的概念:
套接口,也叫“套接字”。是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。
端口号的概念:
在网络技术中,端口大致有两种意思:一是物理意义上的端口,如集线器、交换机、路由器等用于连接其他网络设备的接口。二是指TCP/IP协议中的端口,端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023.例如http的端口号是80,ftp为21,ssh为22,telnet为23等。还有一类是用户自己定义的,通常是大于1024的整型值。
socket概念
Linux中的网络编程是通过socket接口来进行的。socket是一种特殊的I/O接口,它也是一种文件描述符。它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。
每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的;
socket类型
(1)流式socket(SOCK_STREAM)用于TCP通信
流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
(2)数据报socket(SOCK_DGRAM)用于UDP通信
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
(3)原始socket(SOCK_RAW)用于新的网络协议实现的测试等
原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
头文件<netinet/in.h>
Struct sockddr _in
{
Sa_family_t Sin_family;//存储IPV4格式,见man socket
In_port_t sin_port; //存储端口号
Struct in_addr sin_addr;//IP结构体
};
Struct in_addr
{
In_addr_t s_addr;// 存储IP
};
Typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family //拼接成sin_family
sa_family:AF_INET IPv4协议 AF_INET6 IPv6协议
常用的IP和端口转换函数
如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个函数:主机转网络序
htons(),ntohs(),htonl()和ntohl().
这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l来代表。
#include <arpa\inet.h>IPv4的函数原型:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *straddr, struct in_addr *addrptr); //点十进制的IP转化为网络字节序。并保存在在IP结构体
char *inet_ntoa(struct in_addr inaddr);//把网络字节序转化了点十进制IP sockaddr.sin_addr
in_addr_t inet_addr(const char *straddr); //较为常用 将十进制数IP转化为sockaddr.sin_addr.s_addr
函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值。
参数straddr:存放输入的点分十进制数IP地址字符串。
参数addrptr:传出参数,保存网络字节序的32位二进制数值。
函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址。
函数inet_addr():功能与inet_aton相同,但是结果传递的方式不同。inet_addr()若成功则返回32位二进制的网络字节序地址。
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { char ip[] = "192.168.0.101"; struct in_addr myaddr; /* inet_aton */ int iRet = inet_aton(ip, &myaddr); printf("%x\n", myaddr.s_addr); /* inet_addr */ printf("%x\n", inet_addr(ip)); /* inet_pton */ iRet = inet_pton(AF_INET, ip, &myaddr); printf("%x\n", myaddr.s_addr); myaddr.s_addr = 0xac100ac4; /* inet_ntoa */ printf("%s\n", inet_ntoa(myaddr)); /* inet_ntop */ inet_ntop(AF_INET, &myaddr, ip, 16); puts(ip); return 0; }
名字地址转化
通常,人们在使用过程中都不愿意记忆冗长的IP地址,尤其到Ipv6时,地址长度多达128位,那时就更加不可能一次性记忆那么长的IP地址了。因此,使用主机名或域名将会是很好的选择。主机名与域名的区别:主机名通常在局域网里面使用,通过/etc/hosts文件,主机名可以解析到对应的ip;域名通常是再internet上使用。域名例如:www.baidu.com
在linux中,有一些函数可以实现主机名和地址的转化,最常见的有gethostbyname()、gethostbyaddr()等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化。其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。
函数原型:
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
结构体:
struct hostent { char *h_name; /*正式主机名*/ char **h_aliases; /*主机别名*/ int h_addrtype; /*主机IP地址类型 IPv4为AF_INET*/ int h_length; /*主机IP地址字节长度,对于IPv4是4字节,即32位*/ char **h_addr_list; /*主机的IP地址列表*/ }
#define h_addr h_addr_list[0] /*保存的是ip地址*/
函数gethostbyname():用于将域名(www.baidu.com)或主机名转换为IP地址。参数hostname指向存放域名或主机名的字符串。
函数gethostbyaddr():用于将IP地址转换为域名或主机名。参数addr是一个IP地址,此时这个ip地址不是普通的字符串,而是要通过函数inet_aton()转换。len为IP地址的长度,AF_INET为4。family可用AF_INET:Ipv4或AF_INET6:Ipv6。
#include <netdb.h> #include <sys/socket.h> #include <stdio.h> int main(int argc, char **argv) { char *ptr, **pptr; struct hostent *hptr; char str[32] = {'\0'}; /* 取得命令后第一个参数,即要解析的域名或主机名 */ ptr = argv[1]; //如www.baidu.com /* 调用gethostbyname()。结果存在hptr结构中 */ if((hptr = gethostbyname(ptr)) == NULL) { printf(" gethostbyname error for host:%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; /* 将刚才得到的所有地址都打出来。其中调用了inet_ntop()函数 */ for(; *pptr!=NULL; pptr++) printf(" address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str))); printf(" first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str))); break; default: printf("unknown address type\n"); break; } return 0; }
socket编程
使用TCP协议的流程图
服务器端:
1. 头文件包含:
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include <stdio.h>
#include <stdlib.h>
2. socket函数:生成一个套接口描述符(被看作一个文件)。
原型:int socket(int domain,int type,int protocol);
参数:domain{ AF_INET:Ipv4 网络协议 AF_INET6:IPv6网络协议}
type{tcp:SOCK_STREAM udp:SOCK_DGRAM}
protocol 指定socket所使用的传输协议编号。通常为0.
返回值:成功则返回套接口描述符,失败返回-1。
常用实例:int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){perror("socket");exit(-1);}
3. bind函数:用来绑定一个端口号和IP地址,使套接口与指定的端口号和IP地址相关联。
原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);
参数:sockfd为前面socket的返回值。
my_addrà为结构体指针变量
对于不同的socket domain定义了一个通用的数据结构
struct sockaddr //此结构体不常用
{
unsigned short int sa_family; //调用socket()时的domain参数,即AF_INET值。
char sa_data[14]; //最多使用14个字符长度
};
此sockaddr结构会因使用不同的socket domain而有不同结构定义,
例如使用AF_INET domain,其socketaddr结构定义便为
struct sockaddr_in //常用的结构体 { unsigned short int sin_family; //即为sa_family èAF_INET uint16_t sin_port; //为使用的port编号 struct in_addr sin_addr; //为IP 地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr { uint32_t s_addr; };
addrlen sockaddr的结构体长度。通常是计算sizeof(struct sockaddr);
返回值:成功则返回0,失败返回-1
常用实例:struct sockaddr_in my_addr; //定义结构体变量
memset(&my_addr, 0, sizeof(struct sockaddr)); //将结构体清空 //或bzero(&my_addr, sizeof(struct sockaddr)); my_addr.sin_family = AF_INET; //表示采用Ipv4网络协议 my_addr.sin_port = htons(8888); //表示端口号为8888,通常是大于1024的一个值。 //htons()用来将参数指定的16位hostshort转换成网络字符顺序 my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); //inet_addr()用来将IP地址字符串转换成网络所使用的二进制数字,如果为INADDR_ANY,这表示服务器自动填充本机IP地址。 if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1) {perror("bind");close(sfd);exit(-1);}
(注:通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。)
4. listen函数:使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。
原型:int listen(int sockfd,int backlog);
参数:sockfdà为前面socket的返回值.即sfd
backlogà指定同时能处理的最大连接要求,通常为10或者5。最大值可设至128
返回值:成功则返回0,失败返回-1
常用实例:
if(listen(sfd, 10) == -1) {perror("listen");close(sfd);exit(-1);}
5. accept函数:接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求。(也就是说,类似于移动营业厅,如果有客户打电话给10086,此时服务器就会请求连接,处理一些事务之后,就通知一个话务员接听客户的电话,也就是说,后面的所有操作,此时已经于服务器没有关系,而是话务员跟客户的交流。对应过来,客户请求连接我们的服务器,我们服务器先做了一些绑定和监听等等操作之后,如果允许连接,则调用accept函数产生一个新的套接字,然后用这个新的套接字跟我们的客户进行收发数据。也就是说,服务器跟一个客户端连接成功,会有两个套接字。)
原型:int accept(int s,struct sockaddr * addr,int * addrlen);
参数:sà为前面socket的返回值.即sfd
addrà为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。
addrlenà表示结构体的长度,为整型指针
返回值:成功则返回新的socket处理代码new_fd,失败返回-1
常用实例:
struct sockaddr_in clientaddr; memset(&clientaddr, 0, sizeof(struct sockaddr)); int addrlen = sizeof(struct sockaddr); int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen); if(new_fd == -1) {perror("accept");close(sfd);exit(-1);} printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
6. recv函数:用新的套接字来接收远端主机传来的数据,并把数据存到由参数buf 指向的内存空间
原型:int recv(int sockfd,void *buf,int len,unsigned int flags);
参数:sockfdà为前面accept的返回值.即new_fd,也就是新的套接字。
buf 表示缓冲区
len 表示缓冲区的长度
flags 通常为0
返回值:成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1
常用实例:
char buf[512] = {0}; if(recv(new_fd, buf, sizeof(buf), 0) == -1) 最好传长度 来防止阻塞 {perror("recv");close(new_fd);close(sfd);exit(-1);} puts(buf);
7. send函数:用新的套接字发送数据给指定的远端主机
原型:int send(int s,const void * msg,int len,unsigned int flags);
参数:sà为前面accept的返回值.即new_fd
msgà一般为常量字符串
lenà表示长度
flagsà通常为0
返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1
常用实例:
if(send(new_fd, "hello", 6, 0) == -1) {perror("send");close(new_fd);close(sfd);exit(-1);}
8. close函数:当使用完文件后若已不再需要则可使用close()关闭该文件,并且close()会让数据写回磁盘,并释放该文件所占用的资源
原型:int close(int fd);
参数:fdà为前面的sfd,new_fd
返回值:若文件顺利关闭则返回0,发生错误时返回-1
常用实例:
close(new_fd); close(sfd);
//通过TCP实现的服务器文件下载功能
#ifndef __MY_SOCKET_H__ #define __MY_SOCKET_H__ #include <stdio.h> #include "msg.h" #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #define MSG_SIZE 8188 #define MSG_LEN (8192 - MSG_SIZE) typedef struct tag { int s_len ; char s_buf[MSG_SIZE]; } MSG; int listenfd_init(char* ip, char* port); void handle_request(int fd_client); #endif
</pre><pre code_snippet_id="421033" snippet_file_name="blog_20140707_15_168672" name="code" class="cpp"><pre name="code" class="cpp">#include "my_socket.h" int listenfd_init(char* ip, char* port) { int fd_server ; struct sockaddr_in server_addr ; int reuse = 1 ; if((fd_server = socket(AF_INET,SOCK_STREAM, 0) ) == -1) { perror("socket"); exit(-1); } if(0 != setsockopt(fd_server,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))) { perror("setsockopt"); close(fd_server); exit(-1); } memset(&server_addr, 0 , sizeof(server_addr)) ; server_addr.sin_family = AF_INET ; server_addr.sin_port = htons(atoi(port)); server_addr.sin_addr.s_addr = inet_addr(ip); if(-1 == bind(fd_server,(struct sockaddr*)&server_addr,sizeof(server_addr))) { perror("bind"); close(fd_server); exit(-1); } if(-1 == listen(fd_server,5)) { perror("listen"); close(fd_server); exit(-1); } return fd_server ; } void handle_request(int fd_client) { int fd_file ; int read_n ; MSG msg,snd_msg ; memset(&msg,0, sizeof(msg)); recv(fd_client,&msg, MSG_LEN , 0) ; recv(fd_client,&msg.s_buf,msg.s_len,0) ; printf("recv msg :%s \n", msg.s_buf); fd_file = open(msg.s_buf,O_RDONLY); while(memset(&snd_msg,0,sizeof(msg)), (read_n = read(fd_file,snd_msg.s_buf,MSG_SIZE)) > 0) { snd_msg.s_len = read_n ; send(fd_client, &snd_msg, snd_msg.s_len + MSG_LEN ,0); } snd_msg.s_len = 0 ; send(fd_client, &snd_msg, snd_msg.s_len + MSG_LEN ,0); close(fd_file); close(fd_client); }
#include "mysocket.h" int main(int argc,char *argv[])//ip port { if(argc!=3) { printf("too few argument!\n"); exit(-1); } int fd_sever,fd_client,fd_file; int reuse=1;//使IP可以重用 struct sockaddr_in sever_addr,client_addr; int addrlen=sizeof(client_addr); int read_n; // char file_name[128]=""; MSG msg,snd_msg;//8K if((fd_sever=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("open"); exit(-1); } if(0!=setsockopt(fd_sever,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))) { perror("setopt"); close(fd_sever); exit(-1); } memset(&sever_addr,0,sizeof(sever_addr)); sever_addr.sin_family=AF_INET;//ipv4 sever_addr.sin_port=htons(atoi(argv[2])); sever_addr.sin_addr.s_addr=inet_addr(argv[1]); //绑定 if(-1==bind(fd_sever,(struct sockaddr*)&sever_addr,sizeof(sever_addr))) { perror("bind"); close(fd_sever); exit(-1); } printf("bind success!\n"); if(-1==listen(fd_sever,0)) { perror("listen"); close(fd_sever); exit(-1); } printf("listen success\n"); while(fd_client=accept(fd_sever,(struct sockaddr *)&client_addr,&addrlen))//不想接受信息可以不填写结构体 NULL { printf("client connect:%s:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//网络端口和IP转为主机模式 // memset(file_name,0,128); memset(&msg,0,sizeof(msg)); //msg用于保存文件名极其大小 recv(fd_client,&msg.s_len,MSG_LEN,0);//先收长度,再收文件名 recv(fd_client,&msg.s_buf,msg.s_len,0); printf("recvmsg:%s\n",msg.s_buf); fd_file=open(msg.s_buf,O_RDONLY);//snd_msg while(memset(&snd_msg,0,sizeof(snd_msg)),(read_n=read(fd_file,snd_msg.s_buf,MSG_SIZE))>0) { snd_msg.s_len=read_n;//保存每次读取的长度 send(fd_client,&snd_msg,snd_msg.s_len+MSG_LEN,0); } snd_msg.s_len=0; send(fd_client,&snd_msg,snd_msg.s_len+MSG_LEN,0); close(fd_file); close(fd_client); } return 0; }
#include "mysocket.h" int main(int argc,char *argv[]) { if(argc!=3) { printf("too few argument!\n"); exit(-1); } MSG msg,rcv_msg;//一个用于发送文件名。一个用于接受文件 int fd_client,fd_file; struct sockaddr_in sever_addr;//保存服务器端信息 if((fd_client=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); exit(-1); } memset(&sever_addr,0,sizeof(sever_addr)); sever_addr.sin_family=AF_INET; sever_addr.sin_port=htons(atoi(argv[2])); sever_addr.sin_addr.s_addr=inet_addr(argv[1]); //建立与服务器的连接 if(-1==connect(fd_client,(struct sockaddr *)&sever_addr,sizeof(sever_addr))) { perror("connect"); close(fd_client); exit(-1); } //输入要下载的文件名 memset(&msg,0,sizeof(msg)); printf("input:"); scanf("%s",msg.s_buf); fd_file=open(msg.s_buf,O_WRONLY|O_CREAT,0666);//创建对应的文件 msg.s_len=strlen(msg.s_buf);//发送文件名的长度 send(fd_client,&msg,msg.s_len+MSG_LEN,0); //MSG_LEN表示int s_len的长度 msg.s_len存储的是文件名字符串的长度 int total; while(1) { memset(&rcv_msg,0,sizeof(rcv_msg));//接受文件的结构体 recv(fd_client,&rcv_msg,MSG_LEN,0);//先接收字符串长度。存储在rcv_msg.s_len total+=rcv_msg.s_len; system("clear"); printf("downloading...%.2f kb\n",(double)total/1024); if(rcv_msg.s_len>0) { recv(fd_client,rcv_msg.s_buf,rcv_msg.s_len,0); write(fd_file,rcv_msg.s_buf,strlen(rcv_msg.s_buf));//写入文件中 } else { break; } } close(fd_file); close(fd_client); return 0; }