在介绍套接字前,我们需要了解什么是IP地址,什么是端口号port:
1.IP地址
ip协议有两个版本,IPv4 和 IPv6 (一般情况下使用IPv4);
IPv4协议占32个bit;IPv6占128个bit是IPv4的4倍;
1) IP地址是在IP协议中用来标识网络中不同主机的地址;
2) 对于IPv4来说,IP地址是一个4字节,32位的整数
3) 我们通常也是用“ 点分十进制 ”的字符串表示IP地址,例如 192.168.0.1 ;用点分割的每一个数字表示一个字节,范围是 0 ~ 255;
2.端口号
端口号是传输层协议的内容;
1)端口号是一个2字节16位的整数
2)端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
3)IP地址+端口号 能够标识网络上的某一台主机的某一个进程
4)一个端口号只能被一个进程占用
(注:一个进程可以绑多个端口号,但一个端口号只能被一个进程绑定;)
一、套接字简单介绍
1.什么是套接字
在Unix/Linux中,一切皆文件。那对于这两个操作系统而言,“端点”就是一个特殊的文件,也就是说Socket实际上就是文件。既然Socket是文件,那就可以用“打开open –> 读写write/read –> 关闭close”模式来操作它,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
2.对于一个Socket而言,它至少需要3个参数来指定:
1)通信的目的地址;
2)使用的传输层协议(如TCP、UDP);
3)使用的端口号。
3.套接字类型是指创建套接字的应用程序要使用的通信服务类型。linux系统支持多种套接字类型,最常用的有以下三种:
1)SOCK_STREAM:流式套接字,提供面向连接、可靠的数据传输服务,数据按字节流、按顺序收发,保证在传输过程中无丢失、无冗余。TCP协议支持该套接字。
2)SOCK_DGRAM:数据报套接字,提供面向无连接的服务,数据收发无序,不能保证数据的准确到达。UDP协议支持该套接字。
3)SOCK_RAW:原始套接字。允许对低于传输层的协议或物理网络直接访问,例如可以接收和发送ICMP报文。常用于检测新的协议。
4.套接字(socket)编程接口
//创建socket文件描述符(TCP/UDP,客户端 + 服务器)
int socket(int domain,int type,int protocol);
//绑定端口号(TCP/UDP,服务器)
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
//监听(TCP,服务器)
int listen(int socket,int bakclog);
//接受请求,生成新的套接字(TCP,服务器)
int accept(int socket,struct sockaddr* address,socklen_t addrlen);
//建立连接(TCP ,服务器)
int connect(int socket,const struct sockaddr* addr,socklen_t addrlen);
//sockaddr结构
struct sockaddr
{
_SOCKADDR_COMMON( sa_ );
char sa_data[14];
}
//sockaddr_in结构
struct sockaddr_in
{
_SOCKADDR_COMMON(sin_);
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[sizeof(struct sockaddr)-
_SOCKADDR_COMMON_SIZE-
sizeof(in_port)-
sizeof(struct in_addr)];
};
//在基于IPv4编程时,使用的数据结构是sockaddr_in;这个结构体里主要有三部分信息:地址类型、端口号、IP地址
//in_addr结构
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
//in_addr用来表示一个Ipv4的Ip地址。就是一个32位的整数。
5.网络协议中的字节序
所有的网络协议为了统一,全都使用的是大端存储——>下面介绍几个字节序转换函数
#include uint32_t htonl(uint32_t hostlong); //主机序列转换成网络序列
uint16_t htons(uint16_t hostshort); //主机序列转换成网络序列
uint32_t ntohl(uint32_t netlong); //网络序列转换成主机序列
uint16_t ntohs(uint32_t netshort); //网络序列转换成主机序列
二、基于UDP协议编写简单的服务器
1.服务器中的主要操作
(1)创建套接字 int sock=socket( AF_INET , SOCK_DGRAM , 0); 参数SOCK_DGRAM表示UDP。
(2)绑定,bind之后就可以直接进行通信了。
(3)使用sendto和recvfrom来进行数据读写。
2.客户端的主要操作
(1)创建套接字。
(2)使用sendto和recvfrom来进行数据读写。
3.几个重要的函数
//字符串转in_addr函数
#include
int inet_aton(const char*strptr,struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family,const void* addrptr,char* strptr,size_t len);
//in_addr转字符串函数
char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const char* addrptr,void * strptr,size_t len);
//其中inet_pton和inet_ntop不仅可以转换IPv4的addr,也可以转换Ipv6的in6_addr,因此函数接口是void* addrptr 。
4.代码实现
【server.c】
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
int sock=socket(AF_INET,SOCK_DGRAM,0); //创建套接字
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(atoi(argv[2]));
local.sin_addr.s_addr=inet_addr(argv[1]);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) //绑定
{
perror("bind");
return 3;
}
char buf[1024];
struct sockaddr_in client;
while(1)
{
socklen_t len=sizeof(client);
size_t s=recvfrom(sock,buf,sizeof(buf)-1,0,\
(struct sockaddr*)&client,&len); //接收信息
if(s>0)
{
buf[s]=0;
printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port),buf);
sendto(sock,buf,strlen(buf),0,\
(struct sockaddr*)&client,sizeof(client)); //发送信息
}
}
return 0;
}
【client.c】
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
int sock=socket(AF_INET,SOCK_DGRAM,0); //创建套接字
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
char buf[1024];
struct sockaddr_in peer;
while(1)
{
socklen_t len=sizeof(peer);
printf("Please Enter: ");
fflush(stdout);
size_t s=read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1]=0;
sendto(sock,buf,strlen(buf),0,\
(struct sockaddr*)&server,sizeof(server)); //发送信息
size_t _s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len); //接收信息
if(_s>0)
{
buf[_s]=0;
printf("server echo# %s\n",buf);
}
}
}
return 0;
}
三、基于TCP协议编写简单的服务器
1.服务器的主要操作
(1)创建套接字 int sock=socket(AF_INET , SOCK_STREAM , 0); 参数SOCK_STREAM表示TCP
(2)绑定 bind
(3)监听套接字状态 listen
(4)accept监听套接字,获取新的文件描述符
(5)读取信息,发送信息等功能的实现
2.客户端的主要操作
(1)创建套接字 int sock=socket(AF_INET , SOCK_STREAM , 0); 参数SOCK_STREAM表示TCP
(2)连接到服务器 connect
(3)发送信息、接收信息功能的实现
3.几个重要的函数
//(1)bind绑定函数
#include
#include
int bind(int socket,const struct sockaddr* addr,socklen_t addrlen);
//成功返回0,失败返回-1
//(2)listen监听函数
#include
#include
int listen(int socket,int backlog);
//成功返回0,失败返回-1
//backlog 表示的是等待队列的个数,由服务器维护,必须要有但不要太长
//(3)accept接收连接函数
#include
#include
int accept(int socket,struct sockaddr* addr ,socklen_t* addrlen);
//addr是一个传出参数
//返回值是新的进行操作的套接字
//(4)对myaddr参数的初始化
bzero(&servaddr,sizeof(servaddr)); //将整个结构清0
servaddr.sin_family=AF_INET; //设置地址类型位AF_INET
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //INADDR_ANY该宏表示本地的任意IP
servaddr.sin_port=htons(SERV_PORT); //SERV_PORT 表示端口号9999 ,
//(5)connect连接函数
#include
#include
int connect(int socket,const struct sockaddr* addr,socklen_t addrlen);
//客户端用来连接服务器
//connect与bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址
//成功返回0,失败返回-1;
4.代码实现
【server.c】
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define _PORT 9999 //端口号
#define _BACKLOG 10 //等待队列个数
int main()
{
int sock=socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(sock<0)
{
perror("socket!");
return 2;
}
struct sockaddr_in client_sock;
struct sockaddr_in server_sock;
bzero(&server_sock,sizeof(server_sock));
server_sock.sin_family=AF_INET;
server_sock.sin_addr.s_addr=htonl(INADDR_ANY);
server_sock.sin_port=htons(_PORT);
if(bind(sock,(struct sockaddr*)&server_sock,sizeof(struct sockaddr_in))<0) //绑定
{
perror("bind!");
close(sock);
return 3;
}
if(listen(sock,_BACKLOG)<0) //监听
{
perror("listen!");
close(sock);
return 4;
}
printf("bind and listen success,wait accept...\n");
///////////////////////////////////////////
while(1)
{
socklen_t len=0;
int newsock=accept(sock,(struct sockaddr*)&client_sock,&len); //接收连接请求
if(newsock<0)
{
perror("accept!");
close(sock);
return 5;
}
char buf_ip[INET_ADDRSTRLEN];
memset(buf_ip,0,sizeof(buf_ip));
inet_ntop(AF_INET,&client_sock.sin_addr,buf_ip,sizeof(buf_ip));
printf("get connect,ip is:%s port is:%d\n",buf_ip,ntohs(client_sock.sin_port));
while(1)
{
char buf[1024];
int s=read(newsock,buf,sizeof(buf)-1); //接收信息
if(s>1)
{
buf[s]=0;
printf("client:# %s\n",buf);
write(newsock,buf,strlen(buf)); //发送信息
}
else if(s==0)
{
printf("client [ip:%s | %d] exit!\n",buf_ip,ntohs(client_sock.sin_port));
break;
}
}
}
close(sock); //关闭套接字(文件描述符)
return 0;
}
【client.c】
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 9999
//
int main(int argc,char*argv[])
{
if(argc!=2)
{
perror("usage");
return 1;
}
char*str=argv[1];
char buf[1024];
memset(buf,0,sizeof(buf));
int sock=socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(sock<0)
{
perror("socket!");
return 2;
}
struct sockaddr_in server_sock; //
bzero(&server_sock,sizeof(server_sock));
server_sock.sin_family=AF_INET;
inet_pton(AF_INET,str,&server_sock.sin_addr);
server_sock.sin_port=htons(SERVER_PORT);
int ret=connect(sock,(struct sockaddr*)&server_sock,sizeof(server_sock)); //请求连接
printf("connect success...\n");
///////////////////////////////////////////
while(1)
{
printf("client :#");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
write(sock,buf,sizeof(buf));
if(strncasecmp(buf,"quit",4)==0)
{
printf("quit!\n");
break;
}
read(sock,buf,sizeof(buf));
printf("server :$ %s\n",buf);
}
close(sock);
return 0;
}