网络编程
网络编程即如何在程序中实现两台或多台计算机之间的通信。
在进程间通信这一节,我们讲了管道、消息队列等IPC方式,不过只能限制在同一台主机上的进程间通信,满足不了不同主机之间的进程通信,而网络编程中的Socket正好可以满足。现在网络上各种各样的服务大多是基于Socket来完成通信的。
套接字Socket:简单来说是IP地址与端口的结合协议
组成与作用:在网络传输中用于唯一标识两个端点的链接。
端点:包括(IP+Port)
四个要素:客户端的地址、客户端的端口、服务器的地址、服务器端口。
端口号的作用?
主机怎么区分不同的网络服务?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。
实际上是通过IP地址+端口号来区分不同服务的。端口提供了一种访问通道。
TCP协议与UDP协议的区别?
1、TCP面向连接(如打电话要先拨号建立连接),UDP是面向无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
4、每一条TCP连接只能是点到点;UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节。
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
字节序
小端字节序:将低序字节存储在起始地址。
大端字节序:将高序字节存储在起始地址。
例子:在内存中双字0x01020304的存储方式
地址 4000 4001 4002 4003
小端 04 03 02 01
大端 01 02 03 04
Socket服务器和客户端的开发步骤
1、创建套接字
2、为套接字添加信息(IP地址和端口号)
3、监听网络连接
4、监听到有客户端接入,接受一个连接
5、数据交互
6、关闭套接字,断开连接
API介绍
1、创建并初始化Socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数
domain:指明所使用的协议族。通常为AF_INET,表示互联网协议族(TCP/IP协议族);
- AF_INET:IPv4因特网域
- AF_INET6:IPv6因特网域
- AF_UNIX:Unix域
- AF_ROUTE:路由套接字
- AF_KEY:密钥套接字
- AF_UNSPEC:未指定
type参数指定socket的类型:
- SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流,它使用TCP协议,从而保证了数据传输的正确性和顺序性。
- SOCK_DGRAM:数据报文式套接字定义了一种无连接的,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
- SOCK_RAM:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
protocol 通常赋值0。
- 0选择type类型对应的默认协议
- IPPROTO_TCP:TCP传输协议
- IPPROTO_UDP:UDP传输协议
- IPPROTO_STCP:STCP传输协议
- IPPROTO_TIPC:TIPC传输协议
返回
成功的话返回Scoket描述符Sockfd,失败则返回-1。
2、IP端口号与对应描述字赋值函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:用于绑定IP地址和端口号到sockfd
参数
sockfd:Socket描述符
addr:指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族不同而不同。
addrlen:sockaddr结构体类型的长度大小
返回
成功返回0,失败返回-1,并设置errno
//ipv4对应的是:
struct sockaddr {
sa_family_t sa_family;//协议族 char
sa_data[14];//IP+端口
}
同等替换:
struct sockaddr_in
{
sa_family_t sa_family;//协议族
in_port_t sin_port;//端口
struct in_addr sin_addr;//IP地址结构体
unsigned char sin_zero[8]
//填充,没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换
}
地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//把字符串形式的”192.168.1.123”转为网络能识别的格式,即in_addr结构体类型 int inet_aton(const char *cp, struct in_addr *inp);
//把网络格式的ip地址转为字符串形式
char *inet_ntoa(struct in_addr in);
3、监听设置函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:设置能处理的最大连接数,listen函数只能用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动要求与某个进程连接,只能监听是否有其他客户端进程与之连接,然后响应其连接请求,并对它做出处理。
参数
sockfd:socket描述符
backlog:在请求队列中允许的最大请求数。
4、连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:函数由tcp服务器调用,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
参数
sockfd:socket描述符
addr:用于返回已连接的客户端的协议地址
addrlen:客户端地址长度。
返回
返回一个已连接的客户端套接字描述符,失败返回-1,并设置errno
5、字节流读写函数
与I/O中的读取函数略有区别,因为他们输入或输出的字节数可能比请求的少。
#include <unistd.h>
//函数均返回读或写的字节个数,出错则返回-1。
ssize_t read(int sockfd, void *buf, size_t count);
ssize_t write(int sockfd, const void *buf,size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:基于UDP发送数据报,返回实际发送的数据长度,出错时返回-1
参数
sockfd:套接字描述符
buf:指向要发送数据的指针
len:数据长度
flags:通常为0
dest_addr:远端地址(IP地址和端口号)
addrlen:地址长度
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:从UDP接收数据,返回实际接收的字节数,失败时返回-1
参数
sockfd:套接字描述符 buf:指向内存块的指针
len:内存块大小,以字节为单位
flags:通常为0
src_addr:远端地址(IP地址和端口号)
addrlen:地址长度
6、客户端连接主机
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数用于绑定之后的client客户端,与服务器建立连接。
参数
sockfd:目的服务器的socket描述符
addr:服务器端的IP地址和端口号的地址结构指针
addrlen:地址长度常被设置为sizeof(struct sockaddr)
返回
成功返回0,失败返回-1,并设置errno
7、关闭套接字
#include <unistd.h>
int close(int sockfd)
返回
成功返回0,失败返回-1,并设置errno
8、字节序转换
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//返回网络字节序的值
uint16_t htons(uint16_t hostshort);//返回网络字节序的值
uint32_t ntohl(uint32_t netlong);//返回主机字节序的值
uint16_t ntohs(uint16_t netshort);//返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(四个字节),通过上面四个函数来实现主机字节序与网络字节序之间的转换。
小技巧
//跳转到/usr/include路径
cd /usr/include
//在当前路径中的所有文件查找指定内容
grep “struct sockaddr_in {” * -nir
‘*’ 当前路径
-n 显示行号
-i 不区分字母大小
-r 递归查找
TCP协议Socket通信实例
//文件server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("arg error!\n");
exit(-1);
}
int sockfd,c_fd;
struct sockaddr_in s_addr,c_addr;
socklen_t c_size;
char* c_ip="";
char buf[1024]="";
int read_size=0;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(s_addr.sin_addr));
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
printf("socket create fail!\n");
}
if(bind(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in))==-1)
{
printf("bind fail!\n");
exit(-1);
}
else
{
printf("server:%s %d\n",argv[1],ntohs(s_addr.sin_port));
}
if(listen(sockfd,5)==-1)
{
printf("listen fail!\n");
}
c_size=sizeof(struct sockaddr_in);
c_fd=accept(sockfd,(struct sockaddr*)&c_addr,&c_size);
if(c_fd==-1)
{
printf("ip+port get error\n");
perror("why");
close(c_fd);
close(sockfd);
exit(-1);
}
c_ip=inet_ntoa(c_addr.sin_addr);
printf("connect from:%s %d\n",c_ip,ntohs(c_addr.sin_port));
while(1)
{
memset(buf,'\0',1024);
read_size=read(c_fd,buf,1024);
strncpy(buf,buf,read_size);
printf("client:%s\n",buf);
if(strcmp(buf,"exit")==0)
{
printf("client exit!\n");
break;
}
}
close(c_fd);
close(sockfd);
return 0;
}
//文件client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("arg error!\n");
exit(-1);
}
int client_fd;
struct sockaddr_in client_addr;
char buf[1024]="";
memset(&client_addr,0,sizeof(struct sockaddr_in));
client_fd=socket(AF_INET,SOCK_STREAM,0);
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(client_addr.sin_addr));
connect(client_fd,(struct sockaddr*)&client_addr,sizeof(struct sockaddr_in));
while(1)
{
printf("please input data to server:\n");
scanf("%s",buf);
write(client_fd,buf,strlen(buf));
if(strcmp(buf,"exit")==0)
{
break;
}
}
printf("socket exit!\n");
close(client_fd);
return 0;
}
服务器端:
Ubuntu@Embed_Learn:~/learn/socket$./server 127.0.0.1 8888
server:127.0.0.1 8888
connect from:127.0.0.1 40568
client:hello
client:goodbye
client:exit
client exit!
客户端:
Ubuntu@Embed_Learn:~/learn/socket$ ./client 127.0.0.1 8888
please input data to server:
hello
please input data to server:
goodbye
please input data to server:
exit
socket exit!
UDP协议Socket通信实例
//文件server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("arg error!\n");
exit(-1);
}
int server_fd;
struct sockaddr_in server_addr,client_addr;
int sockaddr_size=sizeof(struct sockaddr_in);
char buf[1024]="";
memset(&server_addr,0,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(server_addr.sin_addr));
server_fd=socket(AF_INET,SOCK_DGRAM,0);
if(bind(server_fd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in))==-1)
{
printf("bind fail!\n");
close(server_fd);
exit(-1);
}
int read_size=recvfrom(server_fd,(void*)buf,sizeof(buf),0,(struct sockaddr*)&client_addr,&sockaddr_size);
printf("client ip:%s port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
printf("bytes:%d recv from client:%s\n",read_size,buf);
int write_size=sendto(server_fd,buf,strlen(buf),0,(struct sockaddr*)&client_addr,sockaddr_size);
printf("bytes:%d send from client:%s\n",write_size,buf);
close(server_fd);
return 0;
}
//文件client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("arg error!\n");
exit(-1);
}
int client_fd;
struct sockaddr_in server_addr;
int sockaddr_size=sizeof(struct sockaddr_in);
char buf[1024]="hello!";
memset(&server_addr,0,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(server_addr.sin_addr));
client_fd=socket(AF_INET,SOCK_DGRAM,0);
int write_size=sendto(client_fd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sockaddr_size);
printf("bytes:%d send from server:%s\n",write_size,buf);
memset(buf,'\0',sizeof(buf));
int read_size=recvfrom(client_fd,(void*)buf,sizeof(buf),0,NULL,NULL);
printf("bytes:%d recv from server:%s\n",read_size,buf);
close(client_fd);
return 0;
}
服务器端:
Ubuntu@Embed_Learn:~/learn/socket$./udpserver 127.0.0.1 8888
client ip:127.0.0.1 port:46415
bytes:6 recv from client:hello!
bytes:6 send from client:hello!
客户端:
Ubuntu@Embed_Learn:~/learn/socket$ ./udpclient 127.0.0.1 8888
bytes:6 send from server:hello!
bytes:6 recv from server:hello!
例程:服务器端对多客户端收发消息实例
//文件server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("arg error!\n");
exit(-1);
}
int sockfd,c_fd;
struct sockaddr_in s_addr,c_addr;
socklen_t c_size;
pid_t pid1,pid2;
char* c_ip;
char read_buf[1024]="";
char write_buf[1024]="";
int read_size=0;
int write_size=0;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(s_addr.sin_addr));
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
printf("socket create fail!\n");
}
if(bind(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in))==-1)
{
printf("bind fail!\n");
exit(-1);
}
else
{
printf("server:%s %d\n",argv[1],ntohs(s_addr.sin_port));
}
if(listen(sockfd,5)==-1)
{
printf("listen fail!\n");
}
c_size=sizeof(struct sockaddr_in);
while(1)
{
if((c_fd=accept(sockfd,(struct sockaddr*)&c_addr,&c_size))==-1)
{
printf("ip+port get error\n");
perror("why");
close(c_fd);
}
else
{
c_ip=inet_ntoa(c_addr.sin_addr);
printf("connect from:%s %d\n",c_ip,ntohs(c_addr.sin_port));
if((pid1=fork())<0)
{
printf("fork fail!\n");
close(c_fd);
}
else if(pid1==0)
{
if((pid2=fork())<0)
{
printf("fork fail!\n");
exit(-1);
}
else if(pid2==0)
{
while(1)
{
memset(read_buf,'\0',1024);
if((read_size=read(c_fd,read_buf,1024))==-1)
{
printf("read error!\n");
}
strncpy(read_buf,read_buf,read_size);
printf("bytes:%d client:%s\n",read_size,read_buf);
if(strcmp(read_buf,"exit")==0)
{
printf("read quit!\n");
close(c_fd);
exit(0);
}
}
}
else
{
while(1)
{
scanf("%s",write_buf);
if(write(c_fd,write_buf,strlen(write_buf))==-1)
{
printf("write error!\n");
}
if(strcmp(write_buf,"exit")==0)
{
printf("write quit!\n");
close(c_fd);
exit(0);
}
}
}
}
}
}
close(sockfd);
return 0;
}
//文件client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
pid_t pid;
if(argc!=3)
{
printf("arg error!\n");
exit(-1);
}
int client_fd;
int read_size=0;
int write_size=0;
struct sockaddr_in client_addr;
char write_buf[1024];
char read_buf[1024];
memset(&client_addr,0,sizeof(struct sockaddr_in));
client_fd=socket(AF_INET,SOCK_STREAM,0);
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&(client_addr.sin_addr));
int flag=connect(client_fd,(struct sockaddr*)&client_addr,sizeof(struct sockaddr));
if(flag==-1)
{
printf("connect fail!\n");
exit(-1);
}
printf("connect:%s %s success!\n",argv[1],argv[2]);
if((pid=fork())==-1)
{
printf("fork fail!\n");
}
else if(pid>0)
{
while(1)
{
scanf("%s",write_buf);
if(write(client_fd,write_buf,strlen(write_buf))==-1)
{
printf("write error!\n");
}
if(strcmp(write_buf,"exit")==0)
{
printf("write quit!\n");
break;
}
}
}
else
{
while(1)
{
memset(read_buf,'\0',1024);
if((read_size=read(client_fd,read_buf,1024))==-1)
{
printf("read quit!\n");
}
strncpy(read_buf,read_buf,read_size);
printf("bytes:%d server:%s\n",read_size,read_buf);
if(strcmp(read_buf,"exit")==0)
{
printf("read quit!\n");
break;
}
}
}
close(client_fd);
return 0;
}
服务器端:
Ubuntu@Embed_Learn:~/learn/socket$ ./server 127.0.0.1 8888
server:127.0.0.1 8888
connect from:127.0.0.1 57695
bytes:5 client:hello
bytes:9 client:howareyou
I am ok
bytes:2 client:ok
goodbye
bytes:7 client:goodbye
bytes:4 client:exit
exit
write quit!
read quit!
客户端:
Ubuntu@Embed_Learn:~/learn/socket$ ./client 127.0.0.1 8888
connect:127.0.0.1 8888 success!
hello
how are you
bytes:5 server:Iamok
ok
bytes:7 server:goodbye
goodbye
bytes:4 server:exit
read quit!
exit
write quit!