client.c
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <pthread.h>
#include <windowsx.h>
#pragma comment(lib, "WS2_32")
#define BUFF_LEN 1024
/* 预设服务器地址 */
#define SERVER "127.0.0.1"
/* 预设端口号*/
#define PORT 12345
enum MSG_TYPE
{
MSG_CHAT = 0,
MSG_FILE_NAME,
MSG_FILE_CONTENT,
MSG_FILE_END
};
void showMenu()
{
printf("1. 聊天\n");
printf("2. 发送文件\n");
}
DWORD WINAPI processRead(LPVOID pM)
{
SOCKET s = (SOCKET)pM;
int cmd = 0;
while (1)
{
cmd = 0;
showMenu();
scanf("%d", &cmd);
fflush(stdin);
switch (cmd)
{
case 1:
{
char buffer[BUFF_LEN];
printf("请输入聊天信息: \n");
scanf("%s", buffer);
MSG_TYPE type = MSG_CHAT;
int len = strlen(buffer) + 1;
char *msg = (char *)malloc(strlen(buffer) + 1 + 2 * sizeof(int));
//从源src所指的内存地址的起始位置开始拷贝n个字节
//到目标dest所指的内存地址的起始位置中
memcpy(msg, &type, sizeof(type));
memcpy(msg+sizeof(type), &len, sizeof(len));
memcpy(msg + 2*sizeof(type), buffer, len);
//第一个参数指定发送端套接字描述符;
//第二个参数指明一个存放应用程序要发送数据的缓冲区;
//第三个参数指明实际要发送的数据的字节数;
//第四个参数一般置0。
send(s, msg, strlen(buffer) + 1 + 2 * sizeof(int), 0);
free(msg);
}
break;
case 2:
{
char buffer[BUFF_LEN];
printf("请输入传输文件名: \n");
scanf("%s", buffer);
FILE *fp = fopen(buffer, "r");
if (fp == NULL)
{
printf("文件不存在或者发生未知错误\n");
break;
}
MSG_TYPE type = MSG_FILE_NAME;//MSG_FILE_NAME为1
int len = strlen(buffer) + 1;
char *msg = (char *)malloc(strlen(buffer) + 1 + 2 * sizeof(int));
memcpy(msg, &type, sizeof(type));
memcpy(msg + sizeof(type), &len, sizeof(len));
memcpy(msg + 2 * sizeof(type), buffer, len);
send(s, msg, strlen(buffer) + 1 + 2 * sizeof(int), 0);
free(msg);
while (1)
{
if (feof(fp))
{
Sleep(100);
type = MSG_FILE_END;//MSG_FILE_END 3
send(s, (char *)&type, sizeof(type), 0);
printf("文件发送成功\n");
break;
}
else
{
memset(buffer, BUFF_LEN, 0);
//buffer 用于接收数据的内存地址
//size 要读的每个数据项的字节数,单位是字节
//count 要读count个数据项,每个数据项size个字节.
//stream 输入流
int ret = fread(buffer, 1, BUFF_LEN - 1, fp);
if (ret < 0)
printf("read error\n, %d\n", ret);
printf("file ret %d\n", ret);
buffer[ret] = '\0';
printf("buffer read : %s\n", buffer);
MSG_TYPE t = MSG_FILE_CONTENT;//MSG_FILE_CONTENT 2
int ll = strlen(buffer) + 1;
char *msg = (char *)malloc(ll + 2 * sizeof(int));
memset(msg, ll, 0);
memcpy(msg, &t, sizeof(type));
memcpy(msg + sizeof(type), &ll, sizeof(len));
memcpy(msg + 2 * sizeof(type), buffer, ll);
//send(s, msg, strlen(msg) + 1, 0);
send(s, msg, ll + 2 * sizeof(int), 0);
free(msg);
}
}
}
break;
default:
break;
}
}
return 0;
}
DWORD WINAPI processWrite(LPVOID pM)
{
FILE *fp;
SOCKET s = (SOCKET)pM;
char buffer[BUFF_LEN] = { '\0' };
while (1)
{
memset(buffer, BUFF_LEN, 0);
//第一个参数指定接收端套接字描述符;
//第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
//第三个参数指明buf的长度;
//第四个参数一般置0。
recv(s, buffer, BUFF_LEN, 0);
MSG_TYPE type;
int len;
memcpy(&type, buffer, sizeof(int));
if (type == MSG_FILE_END)
{
fclose(fp);
printf("文件接收成功\n");
continue;
}
memcpy(&len, buffer + sizeof(int), sizeof(int));
char *msg = (char *)malloc(len);
memset(msg, len, 0);
memcpy(msg, buffer + 2 * sizeof(type), len);
if (type == MSG_CHAT)
{
printf(" : %s\n", msg);
}
else if (type == MSG_FILE_NAME)
{
printf("文件名: %s\n", msg);
fp = fopen(msg, "w");
}
else //if(type == MSG_FILE_CONTENT)
{
printf("写文件\n");
if (fp != NULL)
{
printf("recv : %s\n", msg);
fwrite(msg, 1, len - 1, fp);
}
}
free(msg);
}
return 0;
}
int main(int argc, char *argv[])
{
SOCKET client_sockfd;
int len;
struct sockaddr_in address;
char server[UCHAR_MAX];
int result;
if (argc > 1) {
strcpy(server, argv[1]);
}
else {
strcpy(server, SERVER);
}
// 初始化socket dll
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Init socket dll error!");
exit(1);
}
if ((client_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket() 失败");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(SERVER);
address.sin_port = htons(PORT);
len = sizeof(address);
if ((result = connect(client_sockfd, (struct sockaddr *) &address, len)) < 0) {
perror("connect() 呼叫失敗");
closesocket(client_sockfd);
exit(EXIT_FAILURE);
}
else {
printf("连接成功...\n");
}
HANDLE handle1 = CreateThread(NULL, 0, processRead, (LPVOID)client_sockfd, 0, NULL);
HANDLE handle2 = CreateThread(NULL, 0, processWrite, (LPVOID)client_sockfd, 0, NULL);
WaitForSingleObject(handle1, INFINITE);
WaitForSingleObject(handle2, INFINITE);
CloseHandle(handle1);
CloseHandle(handle2);
closesocket(client_sockfd);
//释放winsock库
WSACleanup();
system("pause");
return 0;
}
server.c
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#define PORT 12345 //端口
#pragma comment(lib, "WS2_32") //库
#define BUFF_LEN 1024
int main(int argc, char *argv[]) {
WSADATA wsaData; //windows下结构信息
SOCKET server_sockfd, client_sockfd;
int on = 1;
int server_len, client_len;
struct sockaddr_in server_address; //数据结构
struct sockaddr_in client_address;
int result;
fd_set readfds, testfds;//select
int fdmax;
char buf[BUFF_LEN];
char tmp[BUFF_LEN];
char msg[BUFF_LEN];
timeval tv;
tv.tv_sec = 0; //秒
tv.tv_usec = 500; // 微秒 100 500
int j;
// 初始化socket dll ,请求2.2的版本
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Init socket dll error!");
exit(1);
}
// 创建socket
// 第一个参数,指定地址簇(TCP/IP只能是AF_INET,也可写成PF_INET)
// 第二个,选择套接字的类型(流式套接字),第三个,特定地址家族相关协议(0为自动)
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sockfd == INVALID_SOCKET)
{
printf("socket() 失败");
exit(EXIT_FAILURE);
}
u_long u1 = 1;
//控制套接口的模式,
//FIONBIO 非阻塞设置,非零;阻塞设置,零;
ioctlsocket(server_sockfd, FIONBIO, (u_long*)& u1);
//sin_family表示地址族,对于IP地址,sin_family成员将一直是AF_INET。
//sin_port指定将要分配给套接字的端口。
//sin_addr给出套接字的主机IP地址。
//将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(PORT);
server_len = sizeof(server_address);
//绑定socket和服务端(本地)地址
if (bind(server_sockfd, (sockaddr *)&server_address, server_len) == SOCKET_ERROR)
{
printf("Server Bind Failed: %d", WSAGetLastError());
exit(1);
}
//监听
if (listen(server_sockfd, 10) == -1)
{
printf("Server Listen Failed: %d", WSAGetLastError());
exit(1);
}
FD_ZERO(&readfds); //将 sockset 清空
FD_SET(server_sockfd, &readfds);//把 sockfd 加入到 sockset 集合中
FD_ZERO(&testfds);//将 sockset 清空
memset(&msg, BUFF_LEN, 0);
memset(&tmp, BUFF_LEN, 0);
/* 记录目前fd的数量 */
fdmax = server_sockfd;
printf("服务器已经启动,等待客户上线\n");
for (;;) {
int fd = 0;
/* 复制编号 */
testfds = readfds;//每次在调用select前一定要更新一次
/* 使用 select() 实现多人聊天 */
//这里只监控了读取功能
result = select(0, &testfds, NULL, NULL, &tv);
if (result < 0) {
perror("服务器发生问题\n");
exit(EXIT_FAILURE);
}
/* 遍历 fd_set 扫描所有的文件描述符*/
for (fd = 0; fd < (int)testfds.fd_count; fd++) {
if (testfds.fd_array[fd] == server_sockfd)
{
/*找到相关文件描述符*/
if (FD_ISSET(testfds.fd_array[fd], &testfds)) //有新的连接
{
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address, &client_len);
FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
char ipBuf[20] = { 0 };
// inet_ntop(AF_INET, (void*)&client_address.sin_addr, ipBuf, 16);
inet_ntoa(client_address.sin_addr);//将sin_addr储存的IP(数值)转换成字符串形式(127.0.0.1)
printf("%s: 新连接%s 到 socket#%d\n", argv[0], inet_ntoa(client_address.sin_addr), client_sockfd);
break;
}
}
else
{ //客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据
//第一个参数指定接收端套接字描述符
if (recv(testfds.fd_array[fd], buf, BUFF_LEN - 1, 0) > 0)
{
printf("buf = %s\n", buf);
for (j = 0; j < (int)readfds.fd_count; j++)
{
if ((readfds.fd_array[j] != testfds.fd_array[fd]) && (readfds.fd_array[j] != server_sockfd)) //不发送给自己
send(readfds.fd_array[j], buf, sizeof(buf), 0);
}
}
}
}
}
closesocket(server_sockfd);
//释放winsock库
WSACleanup();
return 0;
}