一、网络编程概述
1.进程间通信:
1)进程间通信的方式有**:管道,消息队列,共享内存,信号,信号量这么集中
2)特点:依赖于linux内核,基本是通过内核来实现应用层的两个进程间的通信
3)缺陷:无法多机通讯
2.网络编程:
1)网络编程适用去不同的pc间的通信,可以实现多机运行
2)它关心的是:地址和数据
地址是指:IP地址和端口号。每台PC机连网够都有一个IP地址,那么每台联网的PC机可能跑多个服务器,然后每个服务器中对应的有很多进程。当客户端接入的时候,不知道去对接哪个服务器。那么这时端口号对应的就是每个服务器的端口。客户就可以通过端口号连接到对应的服务器了。
数据是指:协议(HTTP/TCP/UDP)
它是一种数据格式
3.TCP/UDP对比
1)TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2)TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大的努力交付,即不保证可靠交付
3)TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没用拥塞控制,因此网络出现拥塞不会使源主机的发送速率减低(对实时应用很有用,如IP电话,实时视频会议等)
4)每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5)TCP首部开销字节;UDP的首部开销小,只有8字节
6)TCP的逻辑通信信道是全双工的可靠信道;UDP则是不可靠信道
4.端口号的作用
一台拥有IP地址的主机可以提供许多服务,比如Web服务,FTP服务,SMTP服务
这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务的呢?显然不能只靠IP地址,因为IP地址和网络服务的关系是一对多的关系。
实际上是通过“IP地址+端接口”来区分不同服务的。
端口提供了一种访问通道,
服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69
二、字节序
1.定义
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
2.常见序
1)Little endian(小端字节序):将低序字节存储在起始地址
LE little-endian
最符合人的思维的字节序
地址低位存储值的低位
地址高位存储值的高位
怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说
低位值小,就应该放在内存地址小的地方,也即内存地址低位
反之,高位值就应该放在内存地址大的地方,也即内存地址高位
2) Big endian(大端字节序):将高序字节存储在起始地址
BE big-endian
最直观的字节序
地址低位存储值的高位
地址高位存储值的低位
为什么说直观,不要考虑对应关系
只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出
两者对照,一个字节一个字节的填充进去
三、socket编程步骤
1.创建套接字
函数原型:
#include /* See NOTES */
#include
int socket(int domain, int type, int protocol);
domain:
指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
AF_INET IPv4 因特网域
AF_INET6 IPv6 因特网域
AF_UNIX Uinx 域
AF_ROUTE 路由套接字
AF_KEY 密匙套接字
AF_UNSPEC 未指定
type参数指定socket的类型:
SOCK_STREAM:
流式套接字提供可靠的,面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
SOCK_DGRAM:
数据报套接字定义了一种无连接的服,数据通过相互独立的报文传输,是无序了,并且不保证是可靠,无差错的。它使用数据包协议UDP
SOCK_RAW:
允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发
protocol:
通常赋值:0
0选择type类型对应的默认协议
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTPC传输协议
IPPROTO_TIPC TIPC传输协议
2.bind()函数:IP端口号与相应描述字赋值函数
函数原型:
#include /* See NOTES */
#include
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:
用于绑定IP地址和端口号到socketfd
参数:
sockfd
是一个socket描述符
addr
是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同addrlen
addr
指向的结构体的大小,以字节为单位
struct sockaddr *addr:一般用替换的:
ipv4对应的是:
struct sockaddr {
sa_family_t sa_family;//协议族
char sa_data[14];//IP+端口号
}
同等替换:
struct sockaddr_in{
sa_family_t sin_family;//协议族
in_port_t sin_port;//端口号
struct in_addr sin_addr;//IP地址结构体
unsigned char sin_zero[8];//填充,没用实际意义,只是为sockaddr结构在内存中对齐,这样两者才能相互转换
}
地址转换API
//把字符串形式“127.0.0.1”转化为网络能识别的格式
int inet_aton(const char* straddr,struct in_addr *addrp);
//把网络格式的IP地址转化为字符串形式
char* inet_ntoa(struct in_addr sin_addr);
将主机字节顺序转换为网络字节顺序
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。
为了进行转换 bsd socket提供了转换的函数 有下面四个
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
在使用little endian的系统中 这些函数会把字节序进行转换
在使用big endian类型的系统中 这些函数会定义成空宏
同样 在网络程序开发时 或是跨平台开发时 也应该注意保证只用一种字节序 不然两方的解释不一样就会产生bug.
htonl()函数
头文件
#include
htonl()函数
函数原型是:uint32_t htonl(uint32_t hostlong)
1.hostlong是主机字节顺序表达的32位数,htonl中的h–host主机地址,to–to,n–net网络,l–unsigned long无符号的长整型(32位的系统是4字节);
2.函数返回值是一个32位的网络字节顺序;
3.函数的作用是将一个32位数从主机字节顺序转换成网络字节顺序。
htons()函数
函数原型是:uint16_t htons(uint16_t hostlong)
1.hostlong是主机字节顺序表达的16位数,htons中的h–host主机地址,to–to,n–net网络,s–short long无符号的短整型(32位的系统是2字节);
2.函数返回值是一个16位的网络字节顺序;
3.函数的作用是将一个16位数从主机字节顺序转换成网络字节顺序,简单的说就是把一个16位数高低位呼唤。
ntohs()函数
函数原型是:uint16_t ntohs(uint16_t hostlong)
1.hostlong是网络字节顺序表达的16位数,ntohs中的,n–net网络,to–toh–host主机地址,s–short long有符号的短整型(32位的系统是2字节);
2.函数返回值是一个16位的主机字节顺序;
3.函数的作用是将一个16位数由网络字节顺序转换为主机字节顺序,简单的说就是把一个16位数高低位互换。
ntohl()函数
函数原型是:uint32_t ntohs(uint32_t hostlong)
1.hostlong是网络字节顺序表达的32位数,ntohs中的,n–net网络,to–toh–host主机地址,s–unsigned long无符号的短整型(32位的系统是4字节);
2.函数返回值是一个32位的主机字节顺序;
3.函数的作用是将一个32位数由网络字节顺序转换为主机字节顺序。
大小端
比如: unsigned long hostlong = 0xa2b4c6d8;
大端顺序存放:
偏移地址 存放内容
0x00000000 0xa2
0x00000001 0xb4
0x00000002 0xc6
0x00000003 0xd8
小端顺序存放:
偏移地址 存放内容
0x00000000 0xd8
0x00000001 0xc6
0x00000002 0xb4
0x00000003 0xa2
网络字节顺序一定是大端顺序,主机字节不一定
3.监听
listen()函数:监听设置函数
#include /* See NOTES */
#include
int listen(int sockfd, int backlog);
功能:
设置能处理的最大连接数,listen() 并未开始接收连接,只是设置socket 的 listen 模式,listen 函数只用于服务端,服务器进程不知道要与谁连接,因此,它不会主动要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对他做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动的套接字(监听),规定内核为相应套接字排队的最大连接数。
内核为任何一个给定监听套接字维护两个队列:
未完成连接队列,每个这样的SYN 报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP 三次握手过程。这些套接字处于SYN_REVD 状态;
已完成连接队列,每个已完成TCP 三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED 状态;
参数
sockfd
sockfd 是socket 系统调用返回的服务器端socket 描述符
backlog
backlog 指定在请求队列中允许的最大请求数
4.接受连接
accept()函数
#include /* See NOTES */
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
accept 函数由TCP 服务器调用,用于从已完成连接队列对头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。
参数:
sockfd
sockfd 是socket 系统调用返回的服务器端(s_addr)socket 描述符
addr
用来返回已连接的对端(客户端)的协议地址
addrled
客户端大小(c_addr)
返回值:
该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接收的客户连接创建一个已连接套接字(表示TCP 三次握手已完成),当服务器完成对某个给定客户的服务器时,相应的已连接套接字就会被关闭。
5.数据的收发:
字节流读取函数
在套接字通信中进行字节读取函数:read(); write(); 与I/O 中的读取读取函数略有区别,因为它们输入或输出的字节数比可能比请求的要少。
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
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);
注意:这里fd文字描述符就是套接字。。
6.客户端的connect函数:
connect()函数:客户连接主机
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:
该函数用于绑定之后的client 端(客户端),与服务器建立连接
参数:
sockfd:是目的服务器的socket 描述符
addr:是服务器端的IP 地址和端口号的地址结构指针
addrlen:地址长度,常被设置为sizeof(struct sockaddr)
返回值:
成功返回0,遇到错误时返回 -1,并且error中包含相应的错误码
7.网络消息代码:
1.服务器代码:
#include
#include
#include
//#include
#include
#include
#include
#include
int main()
{
int s_fd;
char readbuf[128] = {0};
int nread;
char *msg = "I get your message,QINGYuan is handsome.\n";
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);
inet_aton("192.168.1.71",&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int clen = sizeof(struct sockaddr_in);
int c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&clen);
if(c_fd == -1){
perror("accept");
exit(-1);
}
printf("start conneting...\n");
sleep(2);
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
//5.read
nread = read(c_fd,readbuf,128);
if(nread == -1){
perror("read");
}else{
printf("get message :%d,%s\n",nread,readbuf);
}
//6.write
if(write(c_fd,msg,strlen(msg))<0){
perror("write");
exit(-1);
}
return 0;
}
运行结果:
客户端代码:
#include
#include
#include
//#include
#include
#include
#include
#include
int main()
{
int c_fd;
char readbuf[128];
int nread;
char *msg="msg from client\n";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
//2.connect
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(8989);
inet_aton("192.168.1.5",&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1){
perror("connect");
exit(-1);
}
//3.write
write(c_fd,msg,strlen(msg));
//4.read
nread=read(c_fd,readbuf,128);
if(nread==-1){
perror("read");
}else{
printf("get message from sever:%d,%s\n",nread,readbuf);
}
return 0;
}
2.多个客户端连接代码:
服务器代码:
#include
#include
#include
//#include
#include
#include
#include
#include
int main(int argc,char **argv)
{
int s_fd;
char readbuf[128];
int nread;
int mark=0;
char msg[128]={0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
if(argc!=3){
printf("param is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(-1);
}
//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
//4.accept
int clen=sizeof(struct sockaddr_in);
while(1){
int c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&clen);
if(c_fd==-1){
perror("accept");
}
printf("connect\n");
mark++;
sleep(2);
printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
if(fork()==0){
if(fork()==0){
while(1){
memset(msg,0,sizeof(msg));
sprintf(msg,"welcome No.%d client",mark);
//6.write
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
while(1){
//5.read
memset(readbuf,0,sizeof(readbuf));
nread=read(c_fd,readbuf,128);
if(nread==-1){
perror("read");
}else{
printf("get message:%d,%s\n",nread,readbuf);
}
}
break;
}
}
return 0;
}
客户端代码:
#include
#include
#include
//#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int c_fd;
char readbuf[128];
int nread;
char *msg="msg from client\n";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
//2.connect
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1){
perror("connect");
exit(-1);
}
//3.write
write(c_fd,msg,strlen(msg));
//4.read
nread=read(c_fd,readbuf,128);
if(nread==-1){
perror("read");
}else{
printf("get message from sever:%d,%s\n",nread,readbuf);
}
return 0;
}
结果: