Linux网络(六)—— 基于pthread的多线程的TCP服务器(阻塞+同步+并发))

上节我们实现了一个简单的多进程的服务器程序,这节,我们服务器的框架不做修改,只是将其修改为一个多线程的服务器程序。

SocketAPI.h

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <cstring>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>

using namespace std;

#define LISTEN_QUEUE 20
#define MAX_FILENAME_SIZE   256
#define BUFFER_SIZE         4096
#define IP_SIZE             20


class SockAPI{
public:
	//创建套接字
	static int Socket(int type)
	{
		int sockfd;
		sockfd = socket(AF_INET, type, 0);
		if(sockfd < 0)
		{
			assert(sockfd >= 0);
			perror("socket error");
			exit(1);
		}
		cout << "socket success\n";
		return sockfd;
	}
	//命名套接字
	static void Bind(int sockfd, int port)
	{
		int ret;
		struct sockaddr_in server_addr;
		server_addr.sin_family = AF_INET;
		//server_addr.sin_addr.s_addr = inet_addr("192.....");
		server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
		server_addr.sin_port = htons(port);
		ret = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
		if(ret < 0)
		{
			assert(ret >= 0);
			perror("bind error");
			exit(1);
		}
		cout << "bind success\n";
	}
	//监听端口
	static void Listen(int sockfd)
	{
		int ret = listen(sockfd, LISTEN_QUEUE);
		if(ret < 0)
		{
			assert(ret = 0);
			perror("listen error");
			exit(1);
		}
		cout << "listening\n";
	}
	//发起连接请求
	static void Connect(int sockfd, char* ip, int port)
	{
		int ret;
		struct sockaddr_in server_addr;
		server_addr.sin_family = AF_INET;
		server_addr.sin_addr.s_addr = inet_addr(ip);
		server_addr.sin_port = htons(port);
		ret = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
		if(ret < 0)
		{
			assert(ret = 0);
			perror("connect error");
			exit(1);
		}
		cout << "connect success\n";
	}
	//接受请求
	static int Accept(int sockfd, string &out_ip, int &out_port)
	{
		int connfd;
		struct sockaddr_in client_addr;
		socklen_t len = sizeof(client_addr);
		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &len);
		if(connfd < 0)
		{
			perror("accept error");
			exit(1);
		}
		out_ip = inet_ntoa(client_addr.sin_addr);
		out_port = htons(client_addr.sin_port);
		cout << "accept success " << out_ip << endl; ;
		return connfd;
	}

	//关闭连接
	static void Close(int connfd)
	{
		close(connfd);
	}

};

服务器

server.h

/**********************************************************
    > File Name: server.c
    > Author: Darwlr
    > Created Time: 2020年08月17日 星期一 12时22分10秒
 *********************************************************/
#include "server.h"

#define TCP_SERVER_PORT     6666    /*  服务器的端口  */

typedef struct 
{
	int socketFd;
    int connFd;
    string ip;
} param_t;


void SignalChild(int signo)
{
	pid_t pid;
	int status;
	while((pid = waitpid(-1, &status, WNOHANG) > 0))
	{
		printf("child %d terminated\n", pid);
	}
	return;
}
void *sendRequest(void *args)
{
	pthread_detach(pthread_self());
	param_t *p = (param_t*)args;
	
    	Server::RaiseClientRequest(p->connFd, p->ip);
	SockAPI::Close(p->connFd);
	exit(0);
}
extern int errno;
int main(int argc, char *argv[])
{
    /**********************************************************
     *
     *  创建并初始化服务器套接字
     *
     **********************************************************/
    int                     socketFd;

    /*  创建套接字  */
	socketFd = SockAPI::Socket(SOCK_STREAM);

    /*  绑定端口  */
	SockAPI::Bind(socketFd, TCP_SERVER_PORT);

    /*  开始监听绑定的端口  */
	SockAPI::Listen(socketFd);
    
    string out_ip;
	int out_port;
   
	pthread_t tid;

	// 添加了对子进程退出的异常处理
	// 子进程结束后,会向父进程发一个 SIGCHLD 信号
	// 注册信号处理函数
	signal(SIGCHLD, SignalChild);
	
    while( 1 )
    {
        /* accept返回一个新的套接字与客户端进行通信  */
		int   connFd;
		connFd = SockAPI::Accept(socketFd, out_ip, out_port);

		// 封装参数
		param_t p;
		p.socketFd = socketFd;
		p.connFd = connFd;
		p.ip = out_ip;
		
        	if(pthread_create(&tid, NULL, sendRequest, (void*)&p) != 0)
		{
			perror("pthread create error");
			break;
		}
  
    }

	SockAPI::Close(socketFd);
   
}

server.cc

#include "SocketAPI.h"
class Server{
public:
	
	/* 服务器接收从客户端传送来的文件  */
	static void TcpServerPullFile(
	            int         connfd,                     /*  服务器与客户端通讯的套接字文件  */
	            string      ip,     /*  与之通信的客户端的信息  */
	            char        *fileServerRoot)            /*  上传文件的存储路径  */
	{
	    char    buffer[BUFFER_SIZE];
	    char    filename[MAX_FILENAME_SIZE];
	    char    fileServerPath[MAX_FILENAME_SIZE]/* = fileServerRoot*/;
	    // 定义文件流
	    FILE    *stream;

	    int     count;              /*  发送文件名的字节数目  */
	    int     dataLength;         /*  接收到的数据大小  */
	    int     writeLength;        /* 实际写入的数据大小  */
	    int     flag = 0;

	    bzero(buffer, BUFFER_SIZE);
	    /*
	     *  向客户端提示输入文件路径提示...
	     *
	     * strcpy(buffer, "请输入要传输的文件的完整路径:");
	    strcat(buffer, "\n");

	    send(new_server_socket, buffer, BUFFER_SIZE, 0);
	    bzero(buffer, BUFFER_SIZE);
	    */

	    /*  首先获取客户端发送过来的文件名  */
	    count = recv(connfd, buffer, BUFFER_SIZE, 0);

	    if(count < 0)
	    {
	        perror("获取文件名失败...\n");
	        exit(1);
	    }
	    else
	    {

	        strncpy(filename, buffer, strlen(buffer) > MAX_FILENAME_SIZE ? MAX_FILENAME_SIZE : strlen(buffer));
	        strcpy(fileServerPath, fileServerRoot);
	        strcat(fileServerPath, filename);
	        printf("\n获取客户端发送过来的文件名成功...\n");
	        printf("文件名[%s]\n", filename);
	        printf("文件存储路径[%s]\n\n", fileServerPath);
	    }

	    //  服务器接受数据, 首先打开一个文件流
	    if((stream = fopen(fileServerPath, "w")) == NULL)
	    {
	        perror("file open error...\n");
	        exit(1);
	    }
	    else
	    {
	        bzero(buffer,BUFFER_SIZE);
	    }

	    printf("正在接收来自%s的文件....\n",ip.c_str());

	    dataLength = 0;


	    /*  先将数据接受到缓冲区buffer中,再写入到新建的文件中  */
	    while((dataLength = recv(connfd, buffer, BUFFER_SIZE, 0)) > 0)
	    {

	        flag++;

	        if(flag == 1)
	        {
	            printf("正在接收来自%s的文件....\n", ip.c_str());
	        }

	        if(dataLength < 0)
	        {
	            printf("接收错误i\n");
	            exit(1);
	        }

	        /*  向文件中写入数据  */
	        writeLength = fwrite(buffer, sizeof(char), dataLength, stream);

	        if(writeLength != dataLength)
	        {
	             printf("file write failed\n");
	             exit(1);
	        }
	        bzero(buffer,BUFFER_SIZE);
	    }

	    if(flag > 0)
	    {
	        printf("%s的文件传送完毕\n", ip.c_str());
	    }
	    if(flag==0)
	    {
	        printf("%s的文件传输失败\n", ip.c_str());
	    }

	    fclose(stream);

	}


	
	/*	服务器将文件发送到客户端
	 *
	 *	当用户选择了下载文件后,服务器将执行此操作
	 *
	 *	*/
	static void TcpServerPushFile(
						int 		connfd, 				 /*  服务器与客户端通讯的套接字文件	*/
						struct		sockaddr_in clientAddr,  /*  与之通信的客户端的信息	*/
						char		*filePath)				/*	带发送至客户端的文件路径  */
	{
		//send file imformation
	
		char	buff[BUFFER_SIZE];
		char	filename[MAX_FILENAME_SIZE];
		int 	count;
		FILE	*stream;
	
	
		/* 先将文件名发送给客户端
		 * 发送文件名时只需要发送filePath最后的文件名filename就可以了
		 * */
		bzero(buff, BUFFER_SIZE);
		strcpy(filename, strrchr(filePath, '/') + 1);
		strncpy(buff, filename, strlen(filename) > MAX_FILENAME_SIZE ? MAX_FILENAME_SIZE : strlen(filename));
		count = send(connfd, buff, BUFFER_SIZE, 0);
		printf("服务器待发送的文件名[%s]..\n", filename);
	
		if(count < 0)
		{
			perror("Send file information");
			exit(1);
		}
	
		/*	服务器开始读取并且发送文件 : */
		if((stream = fopen(filePath, "rb")) == NULL)
		{
			printf("File :%s not found!\n",filePath);
		}
		printf("服务器打开文件成功...\n");
		printf("正在向客户端发送文件...\n");
		bzero(buff, BUFFER_SIZE);
	
		int fileBlockLength = 0;
		while((fileBlockLength = fread(buff, sizeof(char), BUFFER_SIZE, stream)) > 0)
		{
			printf("读取了:%d个数据...\n",fileBlockLength);
			if(send(connfd, buff, fileBlockLength, 0) < 0)
			{
				perror("Send file error...\n");
				perror("向客户端发送文件失败...\n");
				exit(1);
			}
	
			bzero(buff,BUFFER_SIZE);
		}
	
		fclose(stream);
		printf("服务器发送文件成功\n");
	
	}

	// 处理客户端的请求
	static void RaiseClientRequest(int connFd, string ip)
	{

		int count;
		char buffer[BUFFER_SIZE];
		
		while(1)
		{
			printf("\n***************************************************\n");
			// 首先测试接收客户端发来的数据
			printf("===========recv data===========\n");
			bzero(buffer, BUFFER_SIZE);
			if((count = recv(connFd, buffer, BUFFER_SIZE, 0)) < 0)
				 printf("接收来自 %s 的数据错误, 错误码errno = %d....\n", ip, errno);
			else
				printf("接收%d个数据 : %s\n", count, buffer); 
			printf("===========recv data===========\n\n");

			// 向客户端反馈数据
			printf("===========send data===========\n");
			bzero(buffer, BUFFER_SIZE);
			strcpy(buffer, "I am received data");
			if((count = send(connFd, buffer, strlen(buffer) + 1, 0)) < 0)
				printf("发送数据[%s] 失败[错误码 = %d]...\n", buffer, errno);
			else
				 printf("发送数据[%s]成功...\n", buffer);
			printf("===========send data===========\n");


			printf("***************************************************\n");
		}

	}
	
};


客户端

client.h

#include "SocketAPI.h"
class Client{
public:
	
	/* 客户端将文件上传到服务器上 */
	static void TcpClientPushFile(int socketFd, char *filePath)
	{
	    FILE    *stream;
	    char    buffer[BUFFER_SIZE];
	    char    filename[MAX_FILENAME_SIZE];
	    int     count = 0;

	    bzero(buffer, BUFFER_SIZE);
	    strcpy(filename, strrchr(filePath, '/') + 1);
	    strncpy(buffer, filename, strlen(filename) > MAX_FILENAME_SIZE ? MAX_FILENAME_SIZE : strlen(filename));
	    count = send(socketFd, buffer, BUFFER_SIZE, 0);
	    printf("客户端待上传待文件名[%s]..\n", filename);


	    if(count < 0)
	    {
	        perror("Send file information");
	        exit(1);
	    }

	    /*  打开文件流  */
	    if((stream = fopen(filePath, "r")) == NULL)
	    {
	        printf("Can't open the file [%s]\n", filePath);
	        exit(1);
	    }
	    else
	    {
	        printf("客户端打开文件成功\n");
	    }

	    printf("正在向服务器传上传文件...\n");
	    count = 0;

	    /*  清空缓冲区  */
	    bzero(buffer, BUFFER_SIZE);

	    /*  不断读取并发送数据  */
	    while((count = fread(buffer, 1, BUFFER_SIZE, stream)) > 0)
	    {
	        // printf("count =%d\n", count);
	        if(send(socketFd, buffer, count, 0) < 0)
	        {
	            printf("send file error...\n");
	            break;
	        }

	        bzero(buffer, BUFFER_SIZE);  /*  再次将缓冲区清空  */
	    }

	    printf("向服务器发送文件成功...\n");

	    /* 传送完毕后, 关闭文件流  */
	    if(fclose(stream))
	    {
	        printf("file close error\n");
	        exit(1);
	    }
	    else
	    {
	        printf("关闭文件流成功...\n");
	    }


	}




	/* 从服务器上下载文件  */
	static void TcpClientPullFile(int socketFd, char *filePath)
	{
	    char    buff[BUFFER_SIZE];
	    char    filename[MAX_FILENAME_SIZE];
	    int     count, writeLength, dataLength;
	    FILE    *stream;
	    bzero(buff,BUFFER_SIZE);


	    /*  首先获取服务器发送过来的文件名  */
	    count = recv(socketFd, buff, BUFFER_SIZE, 0);

	    if(count < 0)
	    {
	        perror("获取文件名失败...\n");
	        exit(1);
	    }

	    strncpy(filename, buff, strlen(buff) > MAX_FILENAME_SIZE ? MAX_FILENAME_SIZE : strlen(buff));

	    /*  开始接收文件  */
	    printf("Preparing download file : %s", filename);

	    /*  打开文件流  */
	    if((stream = fopen(filename, "wb+")) == NULL)
	    {
	        perror("create file %s error...\n");
	        perror("创建文件失败...\n");
	        exit(1);
	    }

	    bzero(buff, BUFFER_SIZE);          /*  清空缓冲区  */
	    dataLength = 0;
	    while((dataLength = recv(socketFd, buff, BUFFER_SIZE, 0)) != 0)
	    {
	        if(dataLength < 0)  /* 如果接收文件失败  */
	        {
	            perror("download error...\n");
	            perror("下载文件失败...\n");
	            exit(1);
	        }


	        /*  将接收到的文件数据写入文件中  */
	        writeLength = fwrite(buff, sizeof(char), dataLength, stream);
	        if(writeLength < dataLength)   /*  如果写入的数据比实际接收到的数据少  */
	        {
	            perror("file write error...\n");
	            perror("写入文件失败...\n");
	            exit(1);
	        }

	        bzero(buff, BUFFER_SIZE);               /* 清空缓冲区  */
	    }
	    printf("下载来自服务器%s的文件成功\n", filename);
	    printf("Receieved file:%s finished!\n", filename);

	    fclose(stream);             /*  关闭文件流 */

	}

	// 向服务器发送请求
	static void RaiseServerResponse(int socketFd)
	{
		char buffer[BUFFER_SIZE]; 	/*  数据缓冲区              */
		int count;					 /*  接受或者发送的数据大小  */
	
		while(1)
		{
			bzero(buffer, BUFFER_SIZE);
			printf("请输入你要发送的数据:(输入end表示不想发送数据了)\n");
			string msg;
			getline(cin, msg);
			strcpy(buffer, msg.c_str());

			if(strcmp(buffer, "end") == 0)
				break;

			 //  发送数据流
			printf("\n***********************************************************\n");
	   		printf("===========send data===========\n");
			if((count = send(socketFd, buffer, strlen(buffer) + 1, 0)) < 0)
				printf("send data[%s] error[errno = %d]...\n", buffer, errno);
			else
				printf("send data[%s] success...\n", buffer);
			printf("===========send data===========\n\n");

			// 接收数据流
			printf("===========recv data===========\n");
			bzero(buffer, BUFFER_SIZE);
			if((count = recv(socketFd, buffer, BUFFER_SIZE, 0)) < 0)
				printf("recv data[%s] error[errno = %d]...\n", buffer, errno);
			else
				printf("recv data[%s] success...\n", buffer);
			printf("===========recv data===========\n");

			printf("************************************************************\n");
		}

		SockAPI::Close(socketFd);
	}
};

client.cc

/**********************************************************
    > File Name: server.c
    > Author: Darwlr
    > Created Time: 2020年08月17日 星期一 12时22分10秒
 *********************************************************/

#include "client.h"

#define TCP_SERVER_PORT     6666


int main(int argc, char *argv[])
{
    char serverIp[IP_SIZE];             /*  服务器的IP地址       */
	
    if(argc == 1)                  /*  只有一个参数,则默认使用localhost(127.0.0.1)  */
    {
        strcpy(serverIp, "127.0.0.1");
    }
    else
    {
        strcpy(serverIp, argv[1]);
    }

    /**********************************************************
     *
     *  创建并初始化套接字
     *
     **********************************************************/
    int                 socketFd;                     /*  客户端的套接字信息   */


    /*  开始创建套接字                        */
    /*  SOCK_STREAM 面向连接的套接字,即TCP   */
	socketFd = SockAPI::Socket(SOCK_STREAM);

    /*  尝试连接服务器  */
	SockAPI::Connect(socketFd, serverIp, TCP_SERVER_PORT);

    /**********************************************************
     *
     *  下面进行正常的套接字通信
     *
     **********************************************************/
    Client::RaiseServerResponse(socketFd);
    
   // close Listenfd
    SockAPI::Close(socketFd);

}

运行

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值