Socket实现聊天发文件

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值