一.需要认识的函数
1.创建套接字
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明:int socket(int domain, int type, int protocol);
参数:
domain:域。
AF_INET/PF_INET: 网际协议
AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCAL
type:类型。
SOCK_STREAM:流式套接字 TCP
SOCK_DGRAM:数据包套接字 UDP
protocol:协议。 一般为 0
返回值:
成功:待连接套接字
失败:-1
备注:在网际协议中,选择流式套接字就代表 TCP 协议,选择数据包套接字就代表 UDP 协议,
第三个参数 protocol 一般都不用。
2.绑定套接字与网络地址
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:待连接套接字
addr:包含本地地址(IP+PORT)的通用地址结构体的指针
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
备注:
通用地址结构体的定义:
struct sockaddr
{
sa_family_t sa_family
char sa_data[14];
};
特殊地址结构体 —— IPv4 地址结构体:
struct sockaddr_in
{
u_short sin_family; // 地址族
u_short sin_port; // 端口,0-----1023, 1024-----5000,5000------65535
struct in_addr sin_addr; // IPV4 地址
char sin_zero[8];
};
struct in_addr
{
in_addr_t s_addr; // 无符号 32 位网络地址
sockaddr. sin_addr. s_addr = htons(192.168.124.195)
};
特殊地址结构体 —— IPv6 地址结构体:
struct sockaddr_in6
{
u_short sin6_family; // 地址族
__be16 sin6_port; // 端口
__be32 sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // IPv6 地址
__u32 sin6_scope_id;
};
特殊地址结构体 ——UNIX 域地址结构体:
struct sockaddr_un
{
u_short sun_family; // 地址族
char sun_path[108]; // 套接字文件路径
};
3.监听
将待连接套接字设置为监听套接字,并设置最大同时接收连接请求个数
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明:int listen(int sockfd, int backlog);
参数:
sockfd:待连接套接字
backlog:最大同时接收连接请求个数
返回值:
成功:0,并将 sockfd 设置为监听套接字
失败:-1
备注:
由于历史原因,各种系统对 backlog 的理解并不一致,以 LINUX 为例,监听端能同时接收的最大连接请求个数为 0+4。
4.等待对端连接请求
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:监听套接字
addr:通用地址结构体,用以存储对端地址(IP+PORT,结构体如上同理
addrlen:参数 addr 的存储区域大小
返回值:
成功:已连接套接字(非负整数)
失败:-1
5.连接对端监听套接字
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:待连接套接字
addr:包含对端地址(IP+PORT)的通用地址结构体的指针,结构体如上同理
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
6.向 TCP 套接字发送数据
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:即将被发送的数据
len:数据长度
flags:发送标志
{
MSG_NOSIGNAL:当对端已关闭时,不产生 SIGPIPE 信号
MSG_OOB:发送紧急(带外)数据,只针对 TCP 连接
}
返回值:
成功:已发送字节数
失败:-1
备注:
当 flags 为 0 时,send 与 write 作用相同。
7.从 TCP 套接字接收数据
头文件:
#include <sys/types.h>
#include <sys/socket.h>
接口声明: ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:存储数据缓冲区
len:缓冲区大小
flags:接收标志
MSG_OOB:接收紧急(带外)数据
返回值:
成功:已接收字节数
失败:-1
备注:
当 flags 为 0 时,recv 与 read 作用相同。阻塞等待
二.代码演示
1.一个客户端给一个服务端发送消息
①服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)//主函数传参,(先传IP再传端口)argv[1]IP,ARGV[2]端口号
{
//1.创建套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
//网际协议,套接字类型TCP选择流式套接字
if(-1 == sock_fd)
{
perror("creat sock_fd failed");
return -1;
}
//2.绑定服务器的IP地址+端口号
//定义结构体
struct sockaddr_in serve;
serve.sin_family = AF_INET;//地址族成员
//0-------65535 (0----5000,大型企业,5000-----10000,留给将来的大型企业
//10000-----65535,才是留给我们的)
serve.sin_port = htons(atoi(argv[2]));
//需要把点分式字符串转换为无符号的32位网络地址in_addr_t,
serve.sin_addr.s_addr = inet_addr(argv[1]);
//绑定函数
int ret = bind(sock_fd, (struct sockaddr *)&serve, sizeof(serve));
if(ret == -1)
{
perror("bind failed");
}
//3.监听
int listen_ret = listen(sock_fd, 4);//可以设置监听的套接字的最大个数
printf("等待连接....\n");
int acc_fd = accept(sock_fd, NULL, NULL);//阻塞等待
printf("连接成功!!!\n");
char *buf = malloc(100);
//5.畅聊
while(1)
{
memset(buf,0,100);
//接收消息
recv(acc_fd, buf, 100, 0);
printf("客户端:%s\n",buf);
if(strcmp(buf,"gun\n")==0)
{
break;
}
}
close(sock_fd);
close(acc_fd);
}
②客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
//1.创建套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
//网际协议,套接字类型TCP选择流式套接字
if(-1 == sock_fd)
{
perror("creat sock_fd failed");
return -1;
}
//2.绑定服务器地址
struct sockaddr_in serve;
serve.sin_family = AF_INET;//地址族成员
//0-------65535 (0----5000,大型企业,5000-----10000留给将来的大型企业
//10000-----65535,才是留给我们的)
serve.sin_port = htons(atoi(argv[2]));
//需要把点分式字符串转换为无符号的32位网络地址in_addr_t,
serve.sin_addr.s_addr = inet_addr(argv[1]);
//绑定客户端的地址
/*int ret = bind(sock_fd, NULL, 0);
if(ret == -1)
{
perror("bind failed");
}*/
//3.发起连接(connect)
connect(sock_fd,(struct sockaddr *)&serve,sizeof(serve));
char *buf = malloc(100);
while(1)
{
memset(buf,0,100);
fgets(buf,100,stdin);
//4.发送消息(send)
send(sock_fd,buf,100,0);
//发送byebye退出程序
if(strcmp(buf,"byebye\n")==0)
{
break;
}
}
close(sock_fd);
}
2.多个客户端给一个服务器发送消息,并且服务器显示客户端的IP地址和端口号
①服务器
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define JIESHOUSIEZ 100
#define IPPORT 50
#define SOCKET_N 4
//定义已连接套接字和通用地址的结构体
struct client_accept_fp
{
int _accept_fp;
struct sockaddr_in client;
};
void * FWQ(void *fp);
int main(int argc,char**argv)
{
if(argc != 3)//判断参数是否输入正确
{
printf("请输入IP地址和端口号\n");
return -1;
}
//创建套接字文件
int socket_fp = socket(AF_INET, SOCK_STREAM, 0);
//给通用地址的结构体赋值
struct sockaddr_in server,client;
server.sin_family = AF_INET; //地址族
//atoi函数:将字符串转换为int型
//htons函数:将主机的无符号短整形数转换成网络字节顺序
server.sin_port = htons(atoi(argv[2])); //端口号
//inet_addr函数:将IP地址的主机字节序(节点式)转换为网络字节序
server.sin_addr.s_addr = inet_addr(argv[1]); //IPV4地址
socklen_t server_size = sizeof(server);//计算server结构体的大小
socklen_t client_size = sizeof(client);//计算client结构体的大小
//绑定服务器地址
bind(socket_fp, (struct sockaddr *)&server, server_size);
//监听:可以设置监听的套接字的最大个数
listen(socket_fp, SOCKET_N);
pthread_t tid;//定义一个线程号
char* buf = malloc(JIESHOUSIEZ);
//开始聊天
while(1)
{
//等待连接
printf("等待连接......\n");
//等待对端连接请求
int accept_fp = accept(socket_fp,
(struct sockaddr *)&client, &client_size);
//inet_ntoa函数:将IP地址的网络字节序转换为主机字节序(节点式)
//ntohs函数:将网络字节顺序转换成主机的无符号短整形数
printf("客户端[IP:%s PORT:%d]连接成功\n",
inet_ntoa(client.sin_addr),
ntohs(client.sin_port));
//定义已连接套接字和通用地址的结构体变量并且赋值
struct client_accept_fp client_fp;
client_fp._accept_fp = accept_fp;
client_fp.client = client;
//创建线程
pthread_create(&tid, NULL, &FWQ, (void*)(&client_fp));
}
}
//线程执行函数
void * FWQ(void *fp)
{
//不想改变值,不使用指针
struct client_accept_fp client_ret = *(struct client_accept_fp *)fp;
char* buf = malloc(JIESHOUSIEZ);
//开始聊天
while(1)
{
memset(buf,0,JIESHOUSIEZ);//清空
//接收消息
recv(client_ret._accept_fp, buf, JIESHOUSIEZ, 0);
if(strlen(buf) !=0)//如果buf的有效长度为0就不打印
printf("客户端[IP:%s PORT:%d]:%s\n",
inet_ntoa(client_ret.client.sin_addr),
ntohs(client_ret.client.sin_port),buf);
}
}
②客户端
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define JIESHOUSIEZ 100
#define IPPORT 50
int main(int argc,char**argv)
{
if(argc != 3)
{
printf("请输入IP地址和端口号\n");
return -1;
}
//创建套接字文件
int socket_fp = socket(AF_INET, SOCK_STREAM, 0);
//给结构体赋值
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]); //IPV4地址
//绑定客户端的地址
//bind(socket_fp, NULL, 0);
//发送连接
printf("正在发送连接,请稍后\n");
int ret = connect(socket_fp, (struct sockaddr *)&server, sizeof(server));
if(ret == -1)
{
perror("连接失败\n");
return -1;
}
printf("连接成功\n");
char* buf = malloc(JIESHOUSIEZ);
//开始聊天
while(1)
{
memset(buf,0,JIESHOUSIEZ);//清空
fgets(buf,JIESHOUSIEZ,stdin);
//发送消息
send(socket_fp, buf, strlen(buf), 0);
if(strcmp(buf,"byebye\n") == 0) break;
}
close(socket_fp);
return 0;
}
3.一个客户端与一个服务器进行双向通信
①服务器
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define JIESHOUSIEZ 100
#define FASONGSIZE 100
void *sendMessage(void *sm);
int main(int argc,char**argv)
{
if(argc != 3)
{
printf("请输入至少两个参数");
return -1;
}
//创建套接字文件
int socket_fp = socket(AF_INET, SOCK_STREAM, 0);
//给结构体赋值
struct sockaddr_in server,client;
server.sin_family = AF_INET; //地址族
server.sin_port = htons(atoi(argv[2])); //端口号
server.sin_addr.s_addr = inet_addr(argv[1]); //IPV4地址
socklen_t server_size = sizeof(server);
socklen_t client_size = sizeof(client);
//绑定服务器地址
bind(socket_fp, (struct sockaddr *)&server, server_size) ;
//监听:可以设置监听的套接字的最大个数
listen(socket_fp, 4);
//等待连接
printf("等待连接......\n");
int accept_fp = accept(socket_fp, (struct sockaddr *)&client, &client_size);
printf("连接成功\n");
//创建一个线程
pthread_t tid;
pthread_create(&tid,NULL,sendMessage,(void *)&accept_fp);
char* r_buf = malloc(JIESHOUSIEZ);
//开始聊天
while(1)
{
memset(r_buf,0,JIESHOUSIEZ);//清空
//接收消息
recv(accept_fp, r_buf, JIESHOUSIEZ, 0);
if(strlen(r_buf) !=0)
printf("客户端:%s\n",r_buf);
}
}
//线程执行函数
void *sendMessage(void *sm)
{
int* socket = (int*)sm;
char* s_buf = malloc(FASONGSIZE);
while(1)
{
memset(s_buf,0,FASONGSIZE);//清空
fgets(s_buf,FASONGSIZE,stdin);
//发送消息
send(*socket, s_buf, strlen(s_buf), 0);
}
}
②客户端
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define JIESHOUSIEZ 100
#define FASONGSIZE 100
void *receiveMessage(void *rm);
int main(int argc,char**argv)
{
if(argc != 3)
{
printf("请输入至少两个参数");
return -1;
}
//创建套接字文件
int socket_fp = socket(AF_INET, SOCK_STREAM, 0);
//给结构体赋值
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]); //IPV4地址
socklen_t server_size = sizeof(server);
//绑定客户端的地址
//bind(socket_fp, NULL, 0);
//发送连接
printf("正在发送连接,请稍后\n");
int ret = connect(socket_fp, (struct sockaddr *)&server, server_size);
if(ret == -1)
{
perror("连接失败\n");
return -1;
}
printf("连接成功\n");
//创建一个线程
pthread_t tid;
pthread_create(&tid,NULL,receiveMessage,(void *)&socket_fp);
char* buf = malloc(FASONGSIZE);
//开始聊天
while(1)
{
memset(buf,0,FASONGSIZE);//清空
fgets(buf,FASONGSIZE,stdin);
//发送消息
send(socket_fp, buf, strlen(buf), 0);
}
}
//线程执行函数
void *receiveMessage(void *rm)
{
int* socket = (int*)rm;
char* r_buf = malloc(FASONGSIZE);
while(1)
{
memset(r_buf,0,JIESHOUSIEZ);//清空
//接收消息
recv(*socket, r_buf, JIESHOUSIEZ, 0);
if(strlen(r_buf) !=0)
printf("服务器:%s\n",r_buf);
}
}
4.客户端发送普通文件内容给服务器,服务器接收文件内容,并且创建一个普通文件,将接收的文件内容写进创建的普通文件中
①服务器
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define JIESHOUSIEZ 100
int openFile(char *path);
int main(int argc,char**argv)
{
//创建套接字文件
int socket_fp = socket(AF_INET, SOCK_STREAM, 0);
int file_fd = openFile("./1.txt");
//给结构体赋值
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]); //IPV4地址
//绑定服务器地址
bind(socket_fp, (struct sockaddr *)&server, sizeof(server));
//监听:可以设置监听的套接字的最大个数
listen(socket_fp, 4);
//等待连接
printf("等待连接......\n");
int accept_fp = accept(socket_fp, NULL, NULL);
printf("连接成功\n");
char* buf = malloc(JIESHOUSIEZ);
//开始聊天
while(1)
{
memset(buf,0,JIESHOUSIEZ);//清空
//接收消息
recv(accept_fp, buf, JIESHOUSIEZ, 0);
if(strlen(buf) !=0)
{
printf("%s\n",buf);
write(file_fd,buf,strlen(buf));
}
else
{
printf("接收完毕\n");
break;
}
}
close(file_fd);
close(accept_fp);
return 0;
}
int openFile(char *path)
{
int fd = open(path,O_WRONLY|O_CREAT|O_TRUNC,0777);
if(fd == -1)
{
perror("普通文件打开失败!");
return -1;
}
return fd;
}
②客户端
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define JIESHOUSIEZ 100
int openFile(char *path);
int main(int argc,char**argv)
{
//创建套接字文件
int socket_fp = socket(AF_INET, SOCK_STREAM, 0);
int file_ret = openFile(argv[3]); //打开普通文件
//给结构体赋值
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]); //IPV4地址
//绑定客户端的地址
//bind(socket_fp, NULL, 0);
//发送连接
printf("正在发送连接,请稍后\n");
int ret = connect(socket_fp, (struct sockaddr *)&server, sizeof(server));
if(ret == -1)
{
perror("连接失败\n");
return -1;
}
printf("连接成功\n");
char* buf = malloc(JIESHOUSIEZ);
//开始聊天
while(1)//读取文件
{
memset(buf,0,JIESHOUSIEZ);//清空
//发送消息
int read_ret = read(file_ret,buf,JIESHOUSIEZ-1);
send(socket_fp, buf, strlen(buf), 0);
if(read_ret == 0)
{
break;
}
}
close(file_ret);
close(socket_fp);
return 0;
}
int openFile(char *path)
{
int fd = open(path,O_RDONLY);
if(fd == -1)
{
perror("普通文件打开失败!");
return -1;
}
return fd;
}