Linux系统下实现基于TCP/IP协议的简单Socket通信
网络套接字Socket
Socket概念
Socket(套接宇),用来描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发送请求或者应答网络请求!Socket是支持TCP/IP协议的网络通信的基本操作单元,是对网络通信过程中端点的抽象表示,包含了进行网络通信所必须的五种信息:连接所使用的协议;本地主机的IP地址:本地远程的协议端口;远地主机的IP地址以及远地进程的协议端口。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。
注意:在网络通信中,套接字一定是成对出现的。换言之,也就是不在通信时,套接字没有成对这个限制。
网络套接字(Socket)原理可由下图表示:
主机字节序和网络字节序
提到套接字,不得不理解的就是主机字节序和网络字节序,那么什么是主机字节序和网络字节序呢。我们把字节序分为大端字节序(big endian)和小端字节序(little endian),大端字节序指的是一个整数的高位字节存储在内存的低位地址处,低位字节存储在高位地址处;小端字节序指的是一个整数的高位字节存储在内存的高位地址处,低位字节存储在低位地址处(即大端:高存低,低存高;小端反之)。PC端大多采用的是小端字节序,也被称为主机字节序。
对于两台使用不同字节序的主机间进行通信时会出现错误。解决这类问题的方法就是发送端总是把要发送的数据转化成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。因此大端字节序也称为网络字节序,它给所有接收数据的主机提供了一个正确解释收到的格式化数据的保证。
Sockaddr地址结构
TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPv4和IPv6:
Sockaddr地址结构示意图如下:
Sockaddr地址结构表示:
x struct sockadar_in
{
sa_familyt sin family;/*地址族:AF_INET*/
u int16t sin port;/*端口号,要用网络字节序表示*/
struct in addr sin_addr;/*IPv4地址结构体,见下面*/
};
struct in addr
{
uint32ts addr;/*IPv4地址,要用网络字节序表示*/
struct sockaddr_in6
{
sa family_t sin6family;/*地址族:AF_INET6*/
uint16_t sin6port;/*端口号,要用网络字节序表示*/
u int32t sin6 flowinfo;/*流信息,应设置为0*/
struct in6 addr sin6 addr;/*IPv6地址结构体,见下面*/
uint32_t sin6_scope_id;/*scope ID,尚处于实验阶段*/
};
struct in6 addr
{
unsigned char sa_addr[16];/*IPv6地址,要用网络字节序表示*/
};
Socket实现客户端、服务端通信
实现简单的C/S模型可以根据以下流程完成:
服务端程序流程
服务端:(被动连接,需创建自己的地址信息)
- 利用socket函数创建套接字,该函数返回即为用于建立服务端与客户端连接的文件描述符;(称之为句柄)
- 创建服务端地址结构,指定服务端的IP协议簇、IP地址和端口号;(服务端创建自己的地址信息)
- 调用bind函数绑定IP地址和端口;
- 调用listen函数设置监听上限;(同时接受监听的上线数)
- 调用accept函数阻塞监听客户端连接;
- 调用read函数读socket获取的客户端数据信息,并且完成大小写转换,调用write函数向客户端写入数据信息。(这里read,write函数都是使用与服务器进行通信的文件描述符,并不是用于连接的文件描述符)
服务端程序
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
using namespace std;
//定义一个端口号(服务端)
#define SERVER_PORT 9527
//#define BUFSIZ _IO_BUFSIZ
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main (int argc,char *argv[])
{
int link_fd=0;//用于建立连接的文件描述符
int c_fd=0;//用于与服务器进行通信
int ret;
char buf[BUFSIZ];//4096
char client_IP[BUFSIZ];
//创建套接字
link_fd=socket(AF_INET,SOCK_STREAM,0);
//检查返回值,判断是否创建成功
if(link_fd==-1)
{
sys_err("socket error");
}
//创建服务端、客户端结构体 初始化成员
struct sockaddr_in server_addr,client_addr;//引入头文件#include<netinet/in.h>
socklen_t client_addrlen;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERVER_PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//调用bind函数绑定IP地址和端口
ret=bind(link_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
//判断是否绑定成功
if(ret==-1)
{
sys_err("bind error");
}
//设置同时与服务器建立连接的上限
listen(link_fd,128);
//阻塞等待客户端连接
client_addrlen=sizeof(client_addr);
c_fd=accept(link_fd,(struct sockaddr*)&client_addr,&client_addrlen);
//判断是否阻塞等待成功
if(c_fd==-1)
{
sys_err("accept error");
}
//打印客户端IP和端口号
cout<<"client ip:"<<inet_ntop(AF_INET,&(client_addr.sin_addr.s_addr),client_IP,sizeof(client_IP))<<endl;
cout<<"client port:"<<ntohs(client_addr.sin_port)<<endl;
while(1)
{
//读取客户端数据
ret=read(c_fd,buf,sizeof(buf));
//向屏幕写出
write(STDOUT_FILENO,buf,ret);
//转换大小写
for(int i=0;i<ret;i++)
{
buf[i]=toupper(buf[i]);
}
//写回到buf中
write(c_fd,buf,ret);
}
close(link_fd);//关闭套接字
close(c_fd);
return 0;
}
客户端程序流程
客户端:(主动连接,需创建目的地的地址信息)
- socket函数创建套接字,创建用于通信的文件描述符;
- connect函数与服务端建立连接;(客户端无需绑定,系统会自动分配地址信息)
- 调用read函数读取服务端发来的数据信息,write函数向服务端写入数据信息。(这里同样使用的是用于通信的文件描述符)
客户端程序
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
using namespace std;
//定义一个端口号(服务端)
#define SERVER_PORT 9527
//#define BUFSIZ _IO_BUFSIZ
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main (int argc,char *argv[])
{
int ret=0;
int c_fd=0;//创建用于通信的文件描述符
char buf[BUFSIZ];
struct sockaddr_in server_addr;//创建服务端地址结构体
//初始化成员
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERVER_PORT);
//server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//任意有效ip
//将点分十进制的ip地址转化为用于网络传输的二进制数值格式
inet_pton(AF_INET,"127.0.0.1",&(server_addr.sin_addr));
c_fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(c_fd==-1)
{
sys_err("socket error");
}
//客户端与服务端建立连接
ret=connect(c_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret==-1)
{
sys_err("connect error");
}
//循环写入数据
int counter=5;
while(--counter)
{
//客户端写4次hypotic
write(c_fd,"hypotic\n",8);
ret=read(c_fd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
sleep(1);
}
//关闭套接字
close(c_fd);
return 0;
}
运行结果
服务端:
客户端:
参考: