1、基于TCP的套接字编程流程
Server.c
socket
bind (服务器的ip+端口)
listen
accept
recv / send
close
Client.c
socket
connect (服务器的ip+端口)
send / recv
close
扩展:
(1) 三路握手: TCP建立连接时
1)SYN请求 (客户端-->服务器)
2)SYN+ACK应答 (服务器-->客户端)
3)ACK确认 (客户端-->服务器)
(2) 四次挥手: TCP断开连接时
1)FIN请求 (客户端-->服务器)
2)ACK应答 (服务器-->客户端)
3)FIN请求 (服务器-->客户端)
4)ACK应答 (客户端-->服务器)
2、基于UDP的套接字编程流程
Server.c
socket
bind (服务器的ip+端口)
recvfrom / sendto
close
Client.c
socket
//设置服务器的ip和端口
sendto / recvfrom
close
利用UDP 实现简单的通信 UDP ---> SOCK_DGRAM 数据报套接字类型
udp_server.c 接收数据
int main( int argc, char *argv[] )
{
//1.创建套接字 UDP ---> SOCK_DGRAM 数据报套接字
int server_fd = socket( AF_INET, SOCK_DGRAM, 0 );
if( server_fd == -1 )
{
perror("socket server error ");
return -1;
}
printf("server_fd = %d\n", server_fd );
//2.绑定服务器的ip和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; //协议族
server_addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &server_addr.sin_addr ); //IP地址
int re = bind( server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr) );
if( re == -1 )
{
perror("bind server error ");
return -1;
}
printf("bind server success\n");
//3.通信
while( 1 )
{
//接收数据
char buf[128] = {0};
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
re = recvfrom( server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &len );
if( re >= 0 )
{
printf("%s : %s\n", inet_ntoa(client_addr.sin_addr), buf );
}
else
{
perror("recvfrom server error ");
break;
}
//人为定义退出条件
if( buf[0] == '#' )
{
break;
}
}
//4.关闭套接字
close( server_fd );
}
udp_client.c 发送数据
int main( int argc, char *argv[] )
{
//1.创建套接字 UDP ---> SOCK_DGRAM 数据报套接字
int client_fd = socket( AF_INET, SOCK_DGRAM, 0 );
if( client_fd == -1 )
{
perror("socket client error ");
return -1;
}
printf("client_fd = %d\n", client_fd );
//2.设置服务器的ip和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; //协议族
server_addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &server_addr.sin_addr ); //IP地址
//3.通信
while(1)
{
//发送数据
char buf[128] = {0};
printf("input data : ");
fgets(buf, sizeof(buf), stdin);
int re = sendto( client_fd, buf, strlen(buf), 0, (struct sockaddr *)&server_addr, sizeof(server_addr) );
if( re == -1 )
{
perror("sendto error ");
break;
}
//人为定义退出条件
if( buf[0] == '#' )
{
break;
}
}
//4.关闭套接字
close( client_fd );
}
3、UNIX域协议
UNIX域协议是利用socket编程接口 来实现 本地进程之间(客户端/服务器)的通信,它是进程间通信(IPC)的一种方式。它使用文件系统中的路径名来标识服务器和客户端。
UNIX域协议的套接字:
SOCK_STREAM ---> TCP 面向字节流
SOCK_DGRAM ---> UDP 面向数据报
其编程接口 和 流程 与 ipv4协议族是一样的
只不过 协议族为 AF_UNIX , 对应的地址结构体为
UNIX域协议地址结构体 ( man 7 unix )
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sun_family; /* 协议族 AF_UNIX */
char sun_path[108]; /* UNIX域协议的地址,在本地文件系统中的“绝对路径名” pathname */
};
#define UNIX_PATH "/home/china/unix2418F"
实现方法: UDP / TCP
练习:
利用UNIX域协议 实现简单的通信 (以UDP为例)
unix_server.c 接收数据
int main( int argc, char *argv[] )
{
//删除
unlink( UNIX_PATH );
//1.创建套接字 AF_UNIX
int server_fd = socket( AF_UNIX, SOCK_DGRAM, 0 );
if( server_fd == -1 )
{
perror("socket server error ");
return -1;
}
//2.绑定 服务器的地址
struct sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX; //协议族
strcpy( server_addr.sun_path, UNIX_PATH ); //unix域协议的地址
int re = bind( server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr) );
if( re == -1 )
{
perror("bind server error ");
close( server_fd );
return -1;
}
printf("bind server success\n");
struct sockaddr_un client_addr;
socklen_t len = sizeof(client_addr);
//3.通信
while(1)
{
//接收数据
char buf[128] = {0};
re = recvfrom( server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &len );
if( re >= 0 )
{
printf("recv : %s\n", buf );
}
else
{
perror("recvfrom server error ");
break;
}
if( buf[0] == '#' )
{
break;
}
}
//4.关闭套接字
close( server_fd );
}
unix_client.c 发送数据
//1.创建套接字
//2.设置服务器的地址
//3.通信
//4.关闭套接字
4、套接字选项
套接字选项是用来设置或获取套接字的一些特性的选项。
每个套接字在不同的层次上(级别) 有不同的行为属性(选项)
有两个接口函数用来 获取和设置 套接字的选项:
getsockopt
setsockopt
NAME
getsockopt, setsockopt - get and set options on sockets
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
功能:获取/设置套接字的选项
参数:
sockfd:指定要操作的套接字描述符
level:级别,不同的选择在不同的级别上(查看资料)
optname:选项名
optval: 通用指针
get 用来保存获取到的选项值
set 用来保存要设置的选项值
不同的选项 对应的类型是不一样的
如果为 int ---> 0 禁用选项
--> 非0 使能选项
optlen:
get 指针,指向空间保存选项值的空间的长度
set 变量,用来指定要设置的选项值的长度
返回值:
成功,返回0
失败,返回-1,同时errno被设置
设置 端口号重用
选项名: SO_REUSEPORT
级别: SOL_SOCKET
值的类型: int
/设置端口号重用
int n = 1;
setsockopt( server_sock, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n) );
5、UDP的例子 (拓展)
1)DNS : Domain Name System 域名系统
www.baidu.com ----> 域名
DNS:是把一个域名 转换成 相应的ip地址的服务
DNS协议 传输层用到UDP协议
DNS Server
ip: 114.114.114.114
端口 53
2)NTP : Network Time Protocol 网络时间协议
基于UDP的传输层协议
服务器:
阿里云 : 182.92.12.11
端口: 123
利用TCP 写一个网络传输文件的程序
file_tcp_server.c 服务器 接收文件
file_tcp_client.c 客户端 发送文件
注意:
通信双方 发送和接收的 数据、大小、顺序 要保持一致
遵守通信双向的约定 ---> "协议"
file_tcp_server.c 服务器 接收文件
int recv_file( int sock_fd )
{
//切换路径
chdir("../");
//接收文件名的长度大小
int name_len = 0;
int re = recv( sock_fd, &name_len, sizeof(name_len), 0 );
if( re == -1 )
{
perror("recv name_len error ");
return -1;
}
printf("recv name_len success , len = %d\n", name_len );
//接收文件名
char filename[128] = {0};
re = recv( sock_fd, filename, name_len, 0 );
if( re == -1 )
{
perror("recv filename error ");
return -1;
}
printf("recv filename success , filename = %s\n", filename );
//接收文件的大小
int file_size = 0;
re = recv( sock_fd, &file_size, sizeof(file_size), 0 );
if( re == -1 )
{
perror("recv file_size error ");
return -1;
}
printf("recv file_size success , file_size = %d\n", file_size );
/* 接收文件的内容
创建并打开文件
接收数据
写入文件
关闭文件
*/
//创建并打开文件
int fd = open( filename, O_RDWR | O_CREAT | O_EXCL, 0666 );
if( fd == -1 )
{
if( errno == EEXIST ) //文件已经存在,就直接打开即可
{
fd = open( filename, O_RDWR | O_TRUNC );
}
else
{
perror("open file error ");
return -1;
}
}
int write_size = 0; //已经写入的字节数
while( write_size < file_size )
{
//先接收数据
char buf[128] = {0};
re = recv( sock_fd, buf, 100, 0 );
if( re > 0 )
{
//写入文件
write( fd, buf, re );
write_size += re;
}
else
{
perror("recv file error ");
break;
}
}
if( write_size == file_size )
{
printf("recv file success\n");
}
else
{
printf("recv file failed \n");
}
//关闭文件
close( fd );
}
int main( int argc, char *argv[] )
{
//1.创建套接字
int server_fd = socket( AF_INET, SOCK_STREAM, 0 );
if( server_fd == -1 )
{
perror("socket server error ");
return -1;
}
printf("server_fd = %d\n", server_fd );
//2.绑定 服务器的ip和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; //协议族
server_addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &server_addr.sin_addr ); //IP地址
//设置端口号重用
int n = 1;
setsockopt( server_fd, SOL_SOCKET, SO_REUSEPORT, &n, sizeof(n) );
int re = bind( server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr) );
if( re == -1 )
{
perror("bind server error ");
close( server_fd );
return -1;
}
printf("bind server success\n");
//3.监听
re = listen( server_fd, 5 );
if( re == -1 )
{
perror("listen server error ");
close( server_fd );
return -1;
}
printf("listen server success\n");
//4.接受连接
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_fd = accept( server_fd, (struct sockaddr *)&client_addr, &len );
if( client_fd == -1 )
{
perror("accept error ");
close( server_fd );
return -1;
}
printf("client_fd = %d\n", client_fd );
printf("client_ip = %s\n", inet_ntoa(client_addr.sin_addr) );
//5.通信 -->接收文件
recv_file( client_fd );
//6.关闭套接字
close( server_fd );
}
file_tcp_client.c 客户端 发送文件
int send_file( int sock_fd, char * filename )
{
//发送文件名的长度大小
int name_len = strlen(filename);
int re = send( sock_fd, &name_len, sizeof(name_len), 0 );
if( re == -1 )
{
perror("send name_len error ");
return -1;
}
printf("send name_len success , len = %d\n", name_len );
//发送文件名
re = send( sock_fd, filename, name_len, 0 );
if( re == -1 )
{
perror("send filename error ");
return -1;
}
printf("send filename success , filename = %s\n", filename );
//发送文件的大小
struct stat st;
stat( filename, &st ); //获取文件属性-->文件大小
int file_size = st.st_size;
re = send( sock_fd, &file_size, sizeof(file_size), 0 );
if( re == -1 )
{
perror("send file_size error ");
return -1;
}
printf("send file_size success , file_size = %d\n", file_size );
/* 发送文件的内容
打开文件
读取
发送
关闭文件
*/
//打开文件
int fd = open( filename, O_RDONLY );
if( fd == -1 )
{
perror("open file error ");
return -1;
}
int read_size = 0; //已经读取到的字节数
while( read_size < file_size )
{
//先读取文件内容
char buf[128] = {0};
re = read( fd, buf, 100 );
if( re > 0 )
{
//发送数据
send( sock_fd, buf, re, 0 );
read_size += re;
}
else
{
perror("read file error ");
break;
}
}
if( read_size == file_size )
{
printf("send file success\n");
}
else
{
printf("send file failed \n");
}
//关闭文件
close( fd );
}
int main( int argc, char *argv[] )
{
//1.创建套接字
int client_fd = socket( AF_INET, SOCK_STREAM, 0 );
if( client_fd == -1 )
{
perror("socket client error ");
return -1;
}
printf("client_fd = %d\n", client_fd );
//2.设置服务器的ip和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; //协议族
server_addr.sin_port = htons( atoi(argv[2]) ); //端口号
inet_aton( argv[1], &server_addr.sin_addr ); //IP地址
//3.连接服务器
int re = connect( client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr) );
if( re == -1 )
{
perror("connect error ");
close( client_fd );
return -1;
}
printf("connect success\n");
//4.通信 --> 发送文件
send_file( client_fd, argv[3] );
//5.关闭套接字
close( client_fd );
}