socket网络编程之TCP、UDP
系统运维
之前说的用于进程间通信的几种方式:消息signal、管道pipe、消息队列msg、共享内存shm、信号量sem。都只适用于一台主机上的进程间通信,那么如何实现两台计算机之间的进程通信呢?所以,来了解一下异地进程通信。
创新互联专业为企业提供白碱滩网站建设、白碱滩做网站、白碱滩网站设计、白碱滩网站制作等企业网站建设、网页设计与制作、白碱滩企业网站模板建站服务,10多年白碱滩做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。1 异地进程通信 协议层为双方的主机通信进程分配“端口”和缓冲区,以便异地进程间的通信。 1.1TCP/IP协议以下是OSI参考模型与TCP/IP参考模型的对应关系:
1.1.1 TCP/IP协议族TCP/IP 协议组大体上分为三部分:
1.Internet 协议(IP)
2.传输控制协议(TCP)和用户数据报文协议(UDP)
3.处于TCP 和UDP 之上的一组协议专门开发的应用程序。它们包括:TELNET,文件传送协议(FTP),域名服务(dns)和简单的邮件传送程序(SMTP)等许多协议。
应用层协议
流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。
原始套接字。
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。
TCP
UDP
具体函数的用法,就自己man了。
重点讲一下套接字地址结构:
#include < netinet/in.h>
struct sockaddr
{
unsigned short sa_family; /* address族, AF_xxx */
char sa_data[14]; /* 14 bytes的协议地址 */
};
sa_family的取值,一般来说,IPV4使用“AF_INET”
sa_data包含了一些远程电脑的地址、端口和套接字的数目,里面的数据是杂溶在一起的。一般我们不用这个结构体,因为我们一般使用的地址都是IP+端口号。比如:IP192.168.159.2 port3306 。这样来记录地址。所以一般使用下面这个地址结构,而知数据类型是等效的,可以互相转换。
#include < netinet/in.h>
struct sockaddr_in {
short int sin_family; /* Internet地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/
};
2.2.2 字节序列转换
因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,而有的系统是底位在前,高位在后 ),而网络传输的数据大家是一定要统一顺序的。所以对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换。
htons()——“Host to Network Short”主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes) htonl()——“Host to Network Long”
主机字节顺序转换为网络字节顺序(对无符号长型进行操作4bytes) ntohs()——“Network to Host Short”
网络字节顺序转换为主机字节顺序(对无符号短型进行操作2bytes) ntohl()——“Network to Host Long ”
网络字节顺序转换为主机字节顺序(对无符号长型进行操作4bytes) 2.2.3地址格式转换
-linux提供将点分格式的地址转于长整型数之间的转换函数。
inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。inet_ntoa()能够把网络字节顺序转换为地址结构的数据。
2.2.4基本套接字调用socket() bind() connect()
listen() accept() send()
recv() sendto() shutdown()
recvfrom() close() getsockopt()
setsockopt() getpeername()
getsockname() gethostbyname()
gethostbyaddr() getprotobyname()
fcntl()
TCP连接,等待客户端输入,将内容发送给服务器,并获取客户端地址。
这里,getsocketname()表示获得本地(自己)的地址;
getpeername()表示获得连接上的客户端的地址(源IP地址)。
server.c
#include < sys/types.h>
#include < sys/socket.h>
#include < netinet/in.h> //sockaddr_in
#include < stdio.h>
#include < string.h>
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int addrLen = 0;
char acbuf[20] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //连上的客户端的地址
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
//4.阻塞 等待 accept()
clientfd = accept(fd,NULL,NULL);
if(clientfd == -1)
{
perror(accept);
return -1;
}
//获取客户端地址
addrLen = sizeof(struct sockaddr_in);
ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);
if(ret == -1)
{
perror(getpeername);
return -1;
}
printf(client login.\\nip: %s , port: %d\\n,inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
//5.通信
while(1)
{
memset(acbuf,0,20);
if (read(clientfd,acbuf,20) > 0)
{
printf(receive: %s\\n,acbuf);
}
}
//6.close()
close(fd);
return 0;
}
client.c
#include
#include
#include //sockaddr_in
#include
#include
int main()
{
int fd;
int ret;
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
//1.socket();
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.连接connect() 服务器的地址
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1234);
serAddr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(connect);
return -1;
}
//3.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
write(fd,acbuf,strlen(acbuf));
}
//4.close()
close(fd);
return 0;
}
运行结果:
做个改进,以上代码,只能一个客户端连接上。因为TCP是基于点对点的,一个accept()对应一个connnect()。要想连接多个客户端,就得使用fork(),一个进程用来专门阻塞等待客户端的连接,一个用来处理与已连接上客户端的通信。
代码如下:
server.c
int main()
{
int fd;
int clientfd;
int ret;
pid_t pid;
int addrLen = 0;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0}; //自己的地址
struct sockaddr_in clientAddr = {0}; //连上的客户端的地址
signal(SIGCHILD,SIG_IGN);
//1.socket()
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use .
//由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟
//if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
//{
// perror(setsockopet error\\n);
// return -1;
//}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = bind(fd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(bind);
return -1;
}
//3.listen()
ret = listen(fd,10);
if(ret == -1)
{
perror(listen);
return -1;
}
while(1) {
//4.阻塞等待 accept()
clientfd = accept(fd,NULL,NULL);
if(clientfd == -1)
{
perror(accept);
return -1;
}
pid = fork(); //父进程负责继续监听等待,子进程父子与已连接客户端通信
if(pid == -1)
{
perror(fork);
return -1;
}
if(pid == 0) //子进程
{
//获取客户端地址
addrLen = sizeof(struct sockaddr_in);
ret = getpeername(clientfd, (struct sockaddr *)&clientAddr, &addrLen);
if(ret == -1)
{
perror(getpeername);
return -1;
}
sprintf(client_addr,ip: %s , port: %d\\n,\\
inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
printf(client longin.\\n%s\\n,client_addr);
//5.通信
while(1)
{
memset(acbuf,0,20);
if (read(clientfd,acbuf,20) == 0) //客户端退出
{
//结束相应的server进程
close(clientfd);
exit(0); //僵尸进程
}
printf(from %sreceive : %s\\n\\n,client_addr,acbuf);
}
}
else //父进程
{
//返回while,继续等待
}
}
//6.close()
close(fd);
return 0;
}
这里一定要注意,每结束一个客户端,一定要关掉相应的文件描述符,并且结束掉子进程(僵尸进程),不然,随着客户端的增加,进程数会越来越大
client.c
int main()
{
int fd;
int ret;
int addrLen;
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
struct sockaddr_in myAddr = {0};
//1.socket();
fd = socket(PF_INET,SOCK_STREAM,0);
if(fd == -1)
{
perror(socket);
return -1;
}
//2.连接connect() 服务器的地址
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1234);
serAddr.sin_addr.s_addr = inet_addr(192.168.159.6);
ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
if(ret == -1)
{
perror(connect);
return -1;
}
//获取自己的地址
addrLen = sizeof(struct sockaddr_in);
ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);
if(ret == -1)
{
perror(getsockname);
return -1;
}
printf(client---ip: %s , port: %d\\n,\\
inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
//3.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
write(fd,acbuf,strlen(acbuf));
}
//4.close()
close(fd);
return 0;
}
运行结果:
练习2-UDP使用UDP连接,完成上述内容。但是发现,使用UDP,因为是面向无连接的,所以在没有收到或者发送包之前,是无法得知源IP地址的。
那UDP如何知道客户端的IP地址和端口呢?
1、由客户端显示地高速服务器IP地址和端口,发消息。
2、隐式的。服务器从收到的包头中得到源IP和端口号。
server.c
int main()
{
int sockfd;
int ret;
char acbuf[20] = ;
char client_addr[100] = ;
struct sockaddr_in addr = {0};
struct sockaddr_in clientAddr = {0};
int addrLen = sizeof(struct sockaddr_in);
int reuse = 0;
//1.socket()
sockfd = socket(PF_INET,SOCK_DGRAM,0);
if(sockfd == -1)
{
perror(socket);
return -1;
}
//2.bind()
addr.sin_family = AF_INET;
addr.sin_port = htons(1235);
addr.sin_addr.s_addr = inet_addr(127.0.0.1);
ret = bind(sockfd,(struct sockaddr *)&addr,addrLen);
if(ret == -1)
{
perror(bind);
return -1;
}
//3.通信
while(1)
{
memset(acbuf,0,20);
if(recvfrom(sockfd, acbuf, 100,0,(struct sockaddr *)&clientAddr,&addrLen) == -1)
{
perror(recvfrom);
return -1;
}
//收到客户端的数据包之后,就可以知道客户端地址
sprintf(client_addr, ip: %s , port: %d\\n,\\
inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
printf(receive from %s: %s\\n,client_addr,acbuf);
}
//4.close
close(sockfd);
return 0;
}
client.c
int main()
{
int sockfd;
int ret;
int addrLen = sizeof(struct sockaddr_in);
char acbuf[20] = ;
struct sockaddr_in serAddr = {0};
struct sockaddr_in myAddr = {0};
//1.socket()
sockfd = socket(PF_INET,SOCK_DGRAM,0);
if(sockfd == -1)
{
perror(socket);
return -1;
}
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(1235);
serAddr.sin_addr.s_addr = inet_addr(127.0.0.1);
//2.通信
while(1)
{
printf(send: );
fflush(stdout);
scanf(%s,acbuf);
if(strcmp(acbuf,exit) == 0)
{
break;
}
sendto(sockfd, acbuf, 20,0,(struct sockaddr *)&serAddr,addrLen);
//获取自己的地址
ret = getsockname(sockfd,(struct sockaddr *)&myAddr,&addrLen);
if(ret == -1)
{
perror(getsockname);
return -1;
}
printf(client---ip: %s , port: %d\\n\\n,\\
inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
}
//3.close
close(sockfd);
return 0;
}
运行结果:
会发现,此时是可以直接运行多个客户端的,因为,UDP是面向无连接的,可以是一对多,多对一,多对多的,只要客户端知道服务器地址,就可以连上。
Ps:本人理解有限,还未学习完,有错请指出。
分享文章:socket网络编程之TCP、UDP
文章地址:http://scyanting.com/article/cpjshg.html