一、流程图
二、实现
1、服务器
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>
int main(void)
{
/*
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:非负描述符 – 成功,-1 - 出错
1) domain为地址族(Address Family),也就是 IP 地址类型,
常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。
AF_INET 表示 IPv4 地址,例如 127.0.0.1;
AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
127.0.0.1,它是一个特殊IP地址,表示本机地址。
2) type 为数据传输方式/套接字类型,
常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字)
和 SOCK_DGRAM(数据报套接字/无连接的套接字)
3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,
分别表示 TCP 传输协议和 UDP 传输协议。
一般情况下有了 af 和 type 两个参数就可以创建套接字了,
操作系统会自动推演出协议类型,除非遇到这样的情况:
有两种不同的协议支持同一种地址类型和数据传输类型。
如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
*/
//建立套接字
int tcp_fd=socket(AF_INET,SOCK_STREAM,0);
if(tcp_fd<0)
{
printf("socket fail\n");
return -1;
}
else
{
printf("socket......\n");
printf("socket success\n");
}
/*struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。
一、sockaddr
sockaddr在头文件#include <sys/socket.h>中定义,
sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起
struct sockaddr
{
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
二、sockaddr_in
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,
该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
struct sockaddr_in
{
short sin_family;
//Address family一般来说AF_INET(地址族)PF_INET(协议族
unsigned short sin_port;
//Port number(必须要采用网络数据格式,
//普通数字可以用htons()函数转换成网络数据格式的数字)
struct in_addr sin_addr;
//IP address in network byte order(Internet address)
unsigned char sin_zero[8];
//Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐
};
//存放32IP地址
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
*/
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
/*htons 转换程网络字节序
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian),电脑存储小端序
https://www.zhihu.com/question/54580649/answer/145500997
端口的作用:
我们知道一台主机(对应一个IP地址)可以提供很多服务,比如web服务,ftp服务等等。
如果只有一个IP,无法却分不同的网络服务,所以我们采用”IP+端口号”来区分不同的网络服务。
端口的定义:
端口号是标识主机内唯一的一个进程,IP+端口号就可以标识网络中的唯一进程。
在我们通常用的Socket编程中,IP+端口号就是套接字
端口号是由16比特进行编号,范围是0-65535,按照道理来讲,这些端口你都可以随便用。
但是你不是vip用户,所以有一些端口被vip用户占着。
比如FTP 21 Ssh 22等等,所以给端口分了类,规定你可以使用端口的范围。
端口的分类
分类的维度很多,这里我们按照服务端使用还是客户端使用分类
a.服务端使用的端口号 预留端口号
取值范围0-1023,这些端口我们编程的时候不能使用,是那些vip应用程序使用的,
只有超级用户特权的应用才允许被分配一个预留端口号
登记端口号
取值范围1024-49151,就是我们平时编写服务器使用的端口号范围 ,
服务器的端口号,只要在这个范围就好.
客户端使用的端口号
取值范围49152-65535,这部分是客户端进程运行时动态选择的范围,又叫临时端口号*/
server_addr.sin_port=htons(43281);
/* #include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
将字符串转换成32位的网络字节序
char *inet_ntoa(struct in_addr in);
tcp_addr.sin_addr.s_addr=inet_addr(INADDR_ANY)
宏INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,
因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
就比方说我这里本机的ip地址包括:
192.168.150.136 ifconfig
127.0.0.1
0.0.0.0
*/
server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
/*bind函数把一个本地协议地址赋予一个套接字。
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr, socklen_t addrlen);
第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。*/
bind(tcp_fd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr));
/*头文件:#include <sys/socket.h>
定义函数:int listen(int s, int backlog);
函数说明:listen()用来等待参数s 的socket 连线.
参数backlog 指定同时能处理的最大连接要求,
如果连接数目达此上限则client 端将收到ECONNREFUSED 的错误.
Listen()并未开始接收连线, 只是设置socket 为listen 模式,
真正接收client 端连线的是accept().
通常listen()会在socket(), bind()之后调用, 接着才调用accept().
*/
listen(tcp_fd,4);
/*当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);*/
int con_fd=accept(tcp_fd,NULL,NULL);//建立通信套接字
if(con_fd<0)
{
printf("accept failed\n");
return -2;
}
else
{
printf("connect\n");
}
//聊天
char buf[100];
while(1)
{
//清空buffer
bzero(buf,100);
//读取客户端发送的数据
read(con_fd,buf,100);
printf("server:%s\n",buf);
//退出条件
if(!strncmp(buf,"quit",4))
{
break;
}
}
close(con_fd);
close(tcp_fd);
return 0;
}
2、客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>
int main(void)
{
int tcp_fd=socket(AF_INET,SOCK_STREAM,0);//建立带连接套接字
if(tcp_fd<0)
{
printf("socket fail\n");
return -1;
}
else
{
printf("socket......\n");
printf("socket success\n");
}
//拨号,服务器的ip+port
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(43281);//转换程网络字节序
server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//将字符串转换成32位的网络字节序
/*#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, int *addrlen);
返回:若成功则返回0,失败则返回-1;
sockfd是有socket函数返回的套接字描述符,
第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。
套接字地址结构必须含有服务器的IP地址和端口号。*/
int n=connect(tcp_fd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr));
if(n<0)
{
printf("connect failed\n");
return -2;
}
else
{
printf("connect\n");
}
//聊天
char buf[100];
while(1)
{
bzero(buf,100);
fgets(buf,100,stdin);
write(tcp_fd,buf,strlen(buf)); //给服务器发送消息
if(!strncmp(buf,"quit",4))
{
break;
}
}
close(tcp_fd);
return 0;
}
三、效果
服务器
客户端