一,SOCKET
1.1何为socket
socket历史
Socket套接字由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了Socket套接字。Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件sys/socket.h中。通过上面的描述可以得知,套接字对应程序猿来说就是一套网络通信的接口,使用这套接口就可以完成网络通信。
现在来看一下百度百科对于Socket的介绍:套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
我们将一个小区比作一台计算机,一台计算机里面跑了很多程序,怎么区分程序呢,用的是端口,就好像小区用门牌号区分每一户人家一样。手机送到小明家了,怎么进去呢?从大门进啊,怎么找到大门呢?门牌号呀。不就相当于从互联网来的数据找到接收端计算机后再根据端口判断应该给哪一个程序一样吗。小明家的入口就可以用小区地址+门牌号进行唯一表示,那么同样的道理,程序也可以用IP+端口号进行唯一标识。那么这个程序的入口就被称作Socket。
简而言之,socket就是数据你进入程序的大门和你数据出程序的大门。IP地址确定电脑,端口号确定程序,socket就是程序的大门,即数据进入程序最先接触的东西。
1.1,字节序
字节序,就是 大于一个字节类型的数据在内存中的存放顺序。是在跨平台和网络编程中,时常要考虑的问题。也就是说对于单字符来说是没有字节序问题的,字符串是单字符的集合,因此字符串也没有字节序问题。
字节有大端和小端,大端是主机字节序,小端是网络字节序。 在客户端中,绑定端口是需要将端口号从主机字节序转换成网络字节序(htons)。
大端存放(网络字节序):数据的低位字节放在内存的高地址位,数据的高位字节放在内存的低地址位。
小端存放(主机字节序):数据的低位字节放在内存的低地址位,数据的高位字节放在内存的高地址位。
// 有一个16进制的数, 有32位 (int): 0xab5c01ff
// 字节序, 最小的单位: char 字节, int 有4个字节, 需要将其拆分为4份
// 一个字节 unsigned char, 最大值是 255(十进制) ==> ff(16进制)
内存低地址位 内存的高地址位
--------------------------------------------------------------------------->
小端: 0xff 0x01 0x5c 0xab
大端: 0xab 0x5c 0x01 0xff
BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数:htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。
//u:unsigned
// 16: 16位, 32:32位
// h: host, 主机字节序
// n: net, 网络字节序
// s: short
// l: int
端口号是16无符号整型,IP地址是32位无符号整型
查看IP地址,在window和linux中在命令行输入命令如下
# linux
$ ifconfig
# windows
$ ipconfig
注意:连接WIFI的不同,IP地址也不同。但是特殊的IP地址: 127.0.0.1 ==> 和本地的IP地址是等价的
# 假设当前电脑没有联网, 就没有IP地址, 又要做网络测试, 可用使用 127.0.0.1 进行本地测试
1.2 IP地址转换
虽然IP地址本质是一个整形数,但是在使用的过程中都是通过一个字符串来描述,下面的函数描述了如何将一个字符串类型的IP地址进行大小端转换:
// 主机字节序的IP地址转换为网络字节序
// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
// 点分十进制IP -> 大端整形inet_addr() 函数的作用是将点分十进制的IPv4地址转换成网络字节序列的长整型。
in_addr_t inet_addr (const char *cp);
// 大端整形 -> 点分十进制IP inet—ntoa与inet—addr的作用是相反的
char* inet_ntoa(struct in_addr in);
二,TCP的通信流程
1.1socket函数
//创建一个Socket
int socket(int domain, int type, int protocol);
参数:
domain: 使用的地址族协议
AF_INET: 使用IPv4格式的ip地址
AF_INET6: 使用IPv4格式的ip地址
type:
SOCK_STREAM: 使用流式的传输协议
SOCK_DGRAM: 使用报式(报文)的传输协议
protocol: 一般写0即可, 使用默认的协议
SOCK_STREAM: 流式传输默认使用的是tcp
SOCK_DGRAM: 报式传输默认使用的udp
返回值:
成功: 可用于套接字通信的文件描述符
失败: -1
函数的返回值是一个文件描述符,通过这个文件描述符可以操作内核中的某一块内存,网络通信是基于这个文件描述符来完成的
// 将文件描述符和本地的IP与端口进行绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd: 监听的文件描述符, 通过socket()调用得到的返回值
addr: 传入参数, 要绑定的IP和端口信息需要初始化到这个结构体中,IP和端口要转换为网络字节序
addrlen: 参数addr指向的内存大小, sizeof(struct sockaddr)
返回值:成功返回0,失败返回-1
// 给监听的套接字设置监听
int listen(int sockfd, int backlog);
参数:
sockfd: 文件描述符, 可以通过调用socket()得到,在监听之前必须要绑定 bind()
backlog: 同时能处理的最大连接要求,最大值为128
返回值:函数调用成功返回0,调用失败返回 -1
// 等待并接受客户端的连接请求, 建立新的连接, 会得到一个新的文件描述符(通信的)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd: 监听的文件描述符
addr: 传出参数, 里边存储了建立连接的客户端的地址信息
addrlen: 传入传出参数,用于存储addr指向的内存大小
返回值:函数调用成功,得到一个文件描述符, 用于和建立连接的这个客户端通信,调用失败返回
这个函数是一个阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞;当检测到有新的客户端连接请求时,阻塞解除,新连接就建立了,得到的返回值也是一个文件描述符,基于这个文件描述符就可以和客户端通信了。
// 接收数据
ssize_t read(int sockfd, void *buf, size_t size);
ssize_t recv(int sockfd, void *buf, size_t size, int flags);
参数:
sockfd: 用于通信的文件描述符, accept() 函数的返回值
buf: 指向一块有效内存, 用于存储接收是数据
size: 参数buf指向的内存的容量
flags: 特殊的属性, 一般不使用, 指定为 0
返回值:
大于0:实际接收的字节数
等于0:对方断开了连接
-1:接收数据失败了
如果连接没有断开,接收端接收不到数据,接收数据的函数会阻塞等待数据到达,数据到达后函数解除阻塞,开始接收数据,当发送端断开连接,接收端无法接收到任何数据,但是这时候就不会阻塞了,函数直接返回0。
// 发送数据的函数
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
参数:
fd: 通信的文件描述符, accept() 函数的返回值
buf: 传入参数, 要发送的字符串
len: 要发送的字符串的长度
flags: 特殊的属性, 一般不使用, 指定为 0
返回值:
大于0:实际发送的字节数,和参数len是相等的
-1:发送数据失败了
// 成功连接服务器之后, 客户端会自动随机绑定一个端口
// 服务器端调用accept()的函数, 第二个参数存储的就是客户端的IP和端口信息
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd: 通信的文件描述符, 通过调用socket()函数就得到了
addr: 存储了要连接的服务器端的地址信息: iP 和 端口,这个IP和端口也需要转换为大端然后再赋值
addrlen: addr指针指向的内存的大小 sizeof(struct sockaddr)
返回值:连接成功返回0,连接失败返回-1
1.2TCP服务端通信流程
创建用于监听的套接字, 这个套接字是一个文件描述符
int lfd = socket();
将得到的监听的文件描述符和本地的IP 端口进行绑定
bind();
设置监听(成功之后开始监听, 监听的是客户端的连接)
listen();
等待并接受客户端的连接请求, 建立新的连接, 会得到一个新的文件描述符(通信的),没有新连接请求就阻塞
int cfd = accept();
通信,读写操作默认都是阻塞的
// 接收数据
read(); / recv();
// 发送数据
write(); / send();
断开连接, 关闭套接字
close();
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <winSock2.h>
#include <windows.h>
#include <stdbool.h>
#include <sys/types.h>
#include "./fileOperation.h"
#pragma comment(lib, "Ws2.lib") //(lib,"Ws2_32.lib")
#define SOCKET_ERROR (-1)
#define PORT 8888
long g_fileSize = 16158; //文件大小
char* g_fileBuf; // 0~1024系统保留, 一般不用。
#define err(errMsg) printf("[error] %s fail,code %d \
line: %d\n", errMsg, WSAGetLastError(), __LINE__);
bool readFile(const char *fileName);
bool recvFile(SOCKET s,const char* fileName);
bool sendFile(SOCKET s,const char* fileName);
bool saveFile(const char *fileName);
bool init_Socket();
bool close_Socket();
SOCKET create_serverSocket();
int main()
{
//1. 初始化网络库
init_Socket();
// 2.服务器: 创建服务器socket
SOCKET serfd = create_serverSocket();
printf("server creat successed,wait client connect . . .\n");
//等待 客服端连接
SOCKET clifd = accept(serfd,NULL,NULL);
if(clifd== INVALID_SOCKET){
err("accept");
}
printf("有好的问候一下\n");
//可以和客户端通信
if(SOCKET_ERROR == send(clifd,"f**k",4,0)){//最后一个是0,不用管。 4是发送的字符数。
err("f**k send");
}
//sendFile(clifd,"./img3.png");
char recvBuf[1024] = "";
int ret= recv(clifd,recvBuf,1024,0);
if(ret==0){
printf("客户端正常下线..\n");
}
else if(ret<0){
err("ret");
}
puts(recvBuf);
closesocket(clifd);
closesocket(serfd);
// 3.关闭网络库
close_Socket();
getchar();
return 0;
}
bool init_Socket()
{
WSADATA wsadata;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata)) // was windows socket ansyc window 异步套接字
{
err(WSAStartup);
return false;
}
return true;
}
bool close_Socket()
{
if (0 != WSACleanup())
{
err("WSACleanup");
return false;
}
return true;
}
SOCKET create_serverSocket()
{
// 1.创建一个空的socket
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == fd)
{
err("socket");
return INVALID_SOCKET;
}
//~0 对于一个无符号来说是-1, 对于无符号来说是最大值 因为最高位没有存储符号值
// 2.给socket绑定本地ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); // 把本地字节(小端)转化为网络字节序(大端),
addr.sin_addr.S_un.S_addr = inet_addr("192.168.0.112") ; // ADDR_ANY绑定本地任意ip
if (SOCKET_ERROR == bind(fd, (struct sockaddr *)&addr, sizeof(addr)))
{
err("bind");
return INVALID_SOCKET; // 随便返回
}
// 2. 开始监听
listen(fd, 10);
return fd;
}
/**服务器**/
//1.读文件
bool readFile(const char *fileName)
{
FILE *read = fopen(fileName, "rb");
if (!read)
{
perror("file open fail : \n");
return false;
}
//获取文件大小
fseek(read, 0, SEEK_END);//将文件位置指针移动到最后
g_fileSize = ftell(read);
fseek(read, 0, SEEK_SET);//将文件位置指针移到开头
printf("文件大小为 : %d\n", g_fileSize);
//分配内存
g_fileBuf=calloc(g_fileSize,sizeof(char));
if(!g_fileBuf)
return false;
//把文件读取到内存中去
fread(g_fileBuf,sizeof(char),g_fileSize,read);
fclose(read);
return true;
}
//发文件
bool sendFile(SOCKET s,const char* fileName){
readFile(fileName);
int ret = send(s,g_fileBuf,g_fileSize,0);
if(ret == SOCKET_ERROR)
{
perror("send fail : \n");
//return 0;
}
printf("send successed (Byt) : %d \n",ret);
return true;
}
/**客户端***/
//接受文件
bool recvFile(SOCKET s,const char* fileName){
if(g_fileBuf == NULL){
g_fileBuf = calloc(g_fileSize,sizeof(char));
if(!g_fileBuf){
return false;
}
}
int ret=recv(s,g_fileBuf,g_fileSize,0);
if(ret==0){
printf("服务器正常下线..\n");
}
else if(ret<0){
perror("异常下线 : ");
}
saveFile(fileName);
return true;
}
//保存文件
bool saveFile(const char *fileName){
FILE *write = fopen(fileName, "wb");
if (!write)
{
perror("file open fail : \n");
return false;
}
fwrite(g_fileBuf,sizeof(char),g_fileSize,write);
fclose(write);
return true;
}
1.3TCP客服端通信流程
在单线程的情况下客户端通信的文件描述符有一个, 没有监听的文件描述符
创建一个通信的套接字
int cfd = socket();
连接服务器, 需要知道服务器绑定的IP和端口
connect();
通信
// 接收数据
read(); / recv();
// 发送数据
write(); / send();
断开连接, 关闭文件描述符(套接字)
close();
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <winSock2.h>
#include <windows.h>
#include <stdbool.h>
#include <sys/types.h>
#include "./fileOperation.h"
#pragma comment(lib, "Ws2.lib") //(lib,"Ws2_32.lib")
#define PORT 8888 // 0~1024系统保留, 一般不用。
#define SOCKET_ERROR (-1)
bool readFile(const char *fileName);
bool recvFile(SOCKET s,const char* fileName);
bool sendFile(SOCKET s,const char* fileName);
bool saveFile(const char *fileName);
long g_fileSize = 16158; //文件大小
char* g_fileBuf;
#define err(errMsg) printf("[error] %s fail,code %d \
line: %d\n", errMsg, WSAGetLastError(), __LINE__);
bool init_Socket();
bool close_Socket();
SOCKET creat_clientSocket(const char* ip);
int main(){
init_Socket();
SOCKET fd = creat_clientSocket("192.168.0.112"); //192.168.0.112宿舍网络ip地址192.168.29.51 手机热点
char recvBuf[1024] = "";
int ret= recv(fd,recvBuf,1024,0);
if(ret==0){
printf("服务器正常下线..\n");
}
else if(ret<0){
err("ret");
}
puts(recvBuf);
char sendBuf[1024]="你今天好像没洗脸";
send(fd,sendBuf,strlen(sendBuf),0);
//recvFile(fd,"再次成功.png");
closesocket(fd);
close_Socket();
getchar();
return 0;
}
SOCKET creat_clientSocket(const char* ip){
// 1.创建一个空的socket
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == fd)
{
err("socket");
return INVALID_SOCKET;
}
// WINSOCK_API_LINKAGE int WSAAPI connect(SOCKET s,const struct sockaddr *name,int namelen);
// 2.给socket绑定服务端ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); // 把本地字节(小端)转化为网络字节序(大端),
addr.sin_addr.S_un.S_addr =inet_addr(ip); // 绑定服务器ip
if(INVALID_SOCKET == connect(fd, (struct sockaddr *)&addr, sizeof(addr))){
err("connect");
return INVALID_SOCKET;
}
return fd;
}
bool init_Socket()
{
WSADATA wsadata;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata)) // was windows socket ansyc window 异步套接字
{
err("WSAStartup");
return false;
}
return true;
}
bool close_Socket()
{
if (0 != WSACleanup())
{
err("WSACleanup");
return false;
}
return true;
}
/**服务器**/
//1.读文件
bool readFile(const char *fileName)
{
FILE *read = fopen(fileName, "rb");
if (!read)
{
perror("file open fail : \n");
return false;
}
//获取文件大小
fseek(read, 0, SEEK_END);//将文件位置指针移动到最后
g_fileSize = ftell(read);
fseek(read, 0, SEEK_SET);//将文件位置指针移到开头
printf("文件大小为 : %d\n", g_fileSize);
//分配内存
g_fileBuf=calloc(g_fileSize,sizeof(char));
if(!g_fileBuf)
return false;
//把文件读取到内存中去
fread(g_fileBuf,sizeof(char),g_fileSize,read);
fclose(read);
return true;
}
//发文件
bool sendFile(SOCKET s,const char* fileName){
readFile(fileName);
int ret = send(s,g_fileBuf,g_fileSize,0);
if(ret == SOCKET_ERROR)
{
perror("send fail : \n");
//return 0;
}
printf("send successed (Byt) : %d \n",ret);
return true;
}
/**客户端***/
//接受文件
bool recvFile(SOCKET s,const char* fileName){
if(g_fileBuf == NULL){
g_fileBuf = calloc(g_fileSize,sizeof(char));
if(!g_fileBuf){
return false;
}
}
int ret=recv(s,g_fileBuf,g_fileSize,0);
if(ret==0){
printf("服务器正常下线..\n");
}
else if(ret<0){
perror("异常下线 : ");
}
saveFile(fileName);
return true;
}
//保存文件
bool saveFile(const char *fileName){
FILE *write = fopen(fileName, "wb");
if (!write)
{
perror("file open fail : \n");
return false;
}
fwrite(g_fileBuf,sizeof(char),g_fileSize,write);
fclose(write);
return true;
}
注:代码想要在你的电脑运行还需要改一下客服端,服务端的的IP地址,改成你电脑的IP地址
补充:calloc函数
C 库函数 void *calloc(size_t nitems, size_t size) 分配所需的内存空间,并返回一个指向它的指针。malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。
注意:calloc() 函数将分配的内存全部初始化为零。如果不需要初始化,可以使用 malloc() 函数代替。另外,使用 calloc() 函数时需要注意,如果分配的内存块过大,可能会导致内存不足的问题。
声明
下面是 calloc() 函数的声明。
void *calloc(size_t nitems, size_t size)
参数
- nitems -- 要被分配的元素个数。
- size -- 元素的大小。
返回值
该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL。
fopen 函数用于打开文件 , 函数原型如下
FILE *fopen(const char *filename, const char *mode);
const char *filename 参数 : 文件名 ;
const char *mode 参数 : 文件的打开方式 ;
C 库函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 把 ptr 所指向的数组中的数据写入到给定流 stream 中。
声明
下面是 fwrite() 函数的声明。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
参数
- ptr -- 这是指向要被写入的元素数组的指针。
- size -- 这是要被写入的每个元素的大小,以字节为单位。
- nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
返回值
如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误
下面是传输图片代码
servec.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <winSock2.h>
#include <windows.h>
#include <stdbool.h>
#include <sys/types.h>
#include "./fileOperation.h"
#pragma comment(lib, "Ws2.lib") //(lib,"Ws2_32.lib")
#define SOCKET_ERROR (-1)
#define PORT 8888
long g_fileSize = 16158; //文件大小
char* g_fileBuf; // 0~1024系统保留, 一般不用。
#define err(errMsg) printf("[error] %s fail,code %d \
line: %d\n", errMsg, WSAGetLastError(), __LINE__);
bool readFile(const char *fileName);
bool recvFile(SOCKET s,const char* fileName);
bool sendFile(SOCKET s,const char* fileName);
bool saveFile(const char *fileName);
bool init_Socket();
bool close_Socket();
SOCKET create_serverSocket();
int main()
{
//1. 初始化网络库
init_Socket();
// 2.服务器: 创建服务器socket
SOCKET serfd = create_serverSocket();
printf("server creat successed,wait client connect . . .\n");
//等待 客服端连接
SOCKET clifd = accept(serfd,NULL,NULL);
if(clifd== INVALID_SOCKET){
err("accept");
}
/* printf("有好的问候一下\n");
//可以和客户端通信
if(SOCKET_ERROR == send(clifd,"f**k",4,0)){//最后一个是0,不用管。 4是发送的字符数。
err("f**k send");
} */
sendFile(clifd,"./img3.png");
/* char recvBuf[1024] = "";
int ret= recv(clifd,recvBuf,1024,0);
if(ret==0){
printf("客户端正常下线..\n");
}
else if(ret<0){
err("ret");
}
puts(recvBuf); */
closesocket(clifd);
closesocket(serfd);
// 3.关闭网络库
close_Socket();
getchar();
return 0;
}
bool init_Socket()
{
WSADATA wsadata;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata)) // was windows socket ansyc window 异步套接字
{
err(WSAStartup);
return false;
}
return true;
}
bool close_Socket()
{
if (0 != WSACleanup())
{
err("WSACleanup");
return false;
}
return true;
}
SOCKET create_serverSocket()
{
// 1.创建一个空的socket
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == fd)
{
err("socket");
return INVALID_SOCKET;
}
//~0 对于一个无符号来说是-1, 对于无符号来说是最大值 因为最高位没有存储符号值
// 2.给socket绑定本地ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); // 把本地字节(小端)转化为网络字节序(大端),
addr.sin_addr.S_un.S_addr = inet_addr("192.168.0.112") ; // ADDR_ANY绑定本地任意ip
if (SOCKET_ERROR == bind(fd, (struct sockaddr *)&addr, sizeof(addr)))
{
err("bind");
return INVALID_SOCKET; // 随便返回
}
// 2. 开始监听
listen(fd, 10);
return fd;
}
/**服务器**/
//1.读文件
bool readFile(const char *fileName)
{
FILE *read = fopen(fileName, "rb");
if (!read)
{
perror("file open fail : \n");
return false;
}
//获取文件大小
fseek(read, 0, SEEK_END);//将文件位置指针移动到最后
g_fileSize = ftell(read);
fseek(read, 0, SEEK_SET);//将文件位置指针移到开头
printf("文件大小为 : %d\n", g_fileSize);
//分配内存
g_fileBuf=calloc(g_fileSize,sizeof(char));
if(!g_fileBuf)
return false;
//把文件读取到内存中去
fread(g_fileBuf,sizeof(char),g_fileSize,read);
fclose(read);
return true;
}
//发文件
bool sendFile(SOCKET s,const char* fileName){
readFile(fileName);
int ret = send(s,g_fileBuf,g_fileSize,0);
if(ret == SOCKET_ERROR)
{
perror("send fail : \n");
//return 0;
}
printf("send successed (Byt) : %d \n",ret);
return true;
}
/**客户端***/
//接受文件
bool recvFile(SOCKET s,const char* fileName){
if(g_fileBuf == NULL){
g_fileBuf = calloc(g_fileSize,sizeof(char));
if(!g_fileBuf){
return false;
}
}
int ret=recv(s,g_fileBuf,g_fileSize,0);
if(ret==0){
printf("服务器正常下线..\n");
}
else if(ret<0){
perror("异常下线 : ");
}
saveFile(fileName);
return true;
}
//保存文件
bool saveFile(const char *fileName){
FILE *write = fopen(fileName, "wb");
if (!write)
{
perror("file open fail : \n");
return false;
}
fwrite(g_fileBuf,sizeof(char),g_fileSize,write);
fclose(write);
return true;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <winSock2.h>
#include <windows.h>
#include <stdbool.h>
#include <sys/types.h>
#include "./fileOperation.h"
#pragma comment(lib, "Ws2.lib") //(lib,"Ws2_32.lib")
#define PORT 8888 // 0~1024系统保留, 一般不用。
#define SOCKET_ERROR (-1)
bool readFile(const char *fileName);
bool recvFile(SOCKET s,const char* fileName);
bool sendFile(SOCKET s,const char* fileName);
bool saveFile(const char *fileName);
long g_fileSize = 16158; //文件大小
char* g_fileBuf;
#define err(errMsg) printf("[error] %s fail,code %d \
line: %d\n", errMsg, WSAGetLastError(), __LINE__);
bool init_Socket();
bool close_Socket();
SOCKET creat_clientSocket(const char* ip);
int main(){
init_Socket();
SOCKET fd = creat_clientSocket("192.168.0.112"); //192.168.0.112宿舍网络ip地址192.168.29.51 手机热点
/* char recvBuf[1024] = "";
int ret= recv(fd,recvBuf,1024,0);
if(ret==0){
printf("服务器正常下线..\n");
}
else if(ret<0){
err("ret");
}
puts(recvBuf);
char sendBuf[1024]="你今天好像没洗脸";
send(fd,sendBuf,strlen(sendBuf),0); */
recvFile(fd,"再次成功.png");
closesocket(fd);
close_Socket();
getchar();
return 0;
}
SOCKET creat_clientSocket(const char* ip){
// 1.创建一个空的socket
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == fd)
{
err("socket");
return INVALID_SOCKET;
}
// WINSOCK_API_LINKAGE int WSAAPI connect(SOCKET s,const struct sockaddr *name,int namelen);
// 2.给socket绑定服务端ip地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); // 把本地字节(小端)转化为网络字节序(大端),
addr.sin_addr.S_un.S_addr =inet_addr(ip); // 绑定服务器ip
if(INVALID_SOCKET == connect(fd, (struct sockaddr *)&addr, sizeof(addr))){
err("connect");
return INVALID_SOCKET;
}
return fd;
}
bool init_Socket()
{
WSADATA wsadata;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata)) // was windows socket ansyc window 异步套接字
{
err("WSAStartup");
return false;
}
return true;
}
bool close_Socket()
{
if (0 != WSACleanup())
{
err("WSACleanup");
return false;
}
return true;
}
/**服务器**/
//1.读文件
bool readFile(const char *fileName)
{
FILE *read = fopen(fileName, "rb");
if (!read)
{
perror("file open fail : \n");
return false;
}
//获取文件大小
fseek(read, 0, SEEK_END);//将文件位置指针移动到最后
g_fileSize = ftell(read);
fseek(read, 0, SEEK_SET);//将文件位置指针移到开头
printf("文件大小为 : %d\n", g_fileSize);
//分配内存
g_fileBuf=calloc(g_fileSize,sizeof(char));
if(!g_fileBuf)
return false;
//把文件读取到内存中去
fread(g_fileBuf,sizeof(char),g_fileSize,read);
fclose(read);
return true;
}
//发文件
bool sendFile(SOCKET s,const char* fileName){
readFile(fileName);
int ret = send(s,g_fileBuf,g_fileSize,0);
if(ret == SOCKET_ERROR)
{
perror("send fail : \n");
//return 0;
}
printf("send successed (Byt) : %d \n",ret);
return true;
}
/**客户端***/
//接受文件
bool recvFile(SOCKET s,const char* fileName){
if(g_fileBuf == NULL){
g_fileBuf = calloc(g_fileSize,sizeof(char));
if(!g_fileBuf){
return false;
}
}
int ret=recv(s,g_fileBuf,g_fileSize,0);
if(ret==0){
printf("服务器正常下线..\n");
}
else if(ret<0){
perror("异常下线 : ");
}
saveFile(fileName);
return true;
}
//保存文件
bool saveFile(const char *fileName){
FILE *write = fopen(fileName, "wb");
if (!write)
{
perror("file open fail : \n");
return false;
}
fwrite(g_fileBuf,sizeof(char),g_fileSize,write);
fclose(write);
return true;
}
文章参考:套接字-Socket | 爱编程的大丙 (subingwen.cn)