典型C/S模式___多线程实现

版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/84944700

一、思路

1.1 大致轮廓 

图1 多线程实现思路

正如图1 所示的那样,main主线程核心就在于循环探测是否有客户端的连接请求;一旦有客户端的连接请求,就立刻建立连接然后将客户端套接字移交给函数线程。主线程负责建立连接,函数线程负责具体业务的处理。这是非常清晰而且简单的想法。

1.2 要点

主线程如何将建立好的客户端套接字交付给函数线程?

图2 客户端套接字在线程之间传递

进程的所有信息对该进程的所有进程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。 

图3 线程创建函数的说明

第4个参数是一个无类型的指针,调用的时候 &clifd 还是 (void *)clifd 呢?细细品味的话,我们发现只能是后者。因为如果把变量的地址传入的话,main主线程改变该变量值,函数线程中变量的值也会随之改变,毫无疑问,这是我们不希望看到的。 


主线程体能否执行close(clifd)操作?这是不行的,因为这是同一个进程内的不同线程,压根就不存在引用计数的概念。如果在main主线程将clifd即客户端套接字关闭的话,函数线程的就关闭了,因为是共享的,所以导致这个结果。将创建客户端套接字clifd放在main主线程,关闭clifd的工作必须交给函数线程。 

二、服务器

2.1 头文件及函数声明

#include<stdio.h> //printf()
#include<stdlib.h> //atoi()
#include<sys/socket.h>
#include<unistd.h> //close()
#include<arpa/inet.h> //inet_addr()
#include<netinet/in.h> //htons()
#include<pthread.h> //pthread_create()
#include<assert.h> //assert()
#include<string.h> //strlen()
#define SIZE 128 //用于接受数据的数组大小
int create_socket(const char *ip, int port, int num); //tcp编程流程
void *fun_cb(void *arg); //作为线程的回调函数使用

2.2 main主函数

int main(int argc, char *argv[])
{
    if(argc <= 2)
    {
	    printf("argument is not enough.\n");
	    return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    int listenfd = create_socket(ip, port, 5); //tcp编程流程

    while(1)
    {
	    struct sockaddr_in cli;
	    socklen_t len = sizeof(cli);
	    int clifd = accept(listenfd, (struct sockaddr*)&cli, &len);
	    if(clifd < 0)
	    {
	        printf("link error.\n");
	        continue;
	    }
	    pthread_t t;
	    int res = pthread_create(&t, NULL, fun_cb, (void*)clifd);
	    assert(res != -1);
    }

    close(listenfd); //关闭监听套接字
    return 0;
}

2.3 其他函数

int create_socket(const char *ip, int port, int num) //tcp编程流程
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0); //生成套接字
    assert(-1 != listenfd);
    struct sockaddr_in ser;
    
    ser.sin_family = AF_INET;
    ser.sin_port = htons(port);
    ser.sin_addr.s_addr= inet_addr(ip);
    
    int res = 0;
    res = bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));
    assert(-1 != res);
    
    res = listen(listenfd, num);
    assert(-1 != res);
    
    return listenfd;
}

void *fun_cb(void *arg) //作为线程的回调函数使用
{
    int clifd = (int)arg; //获取客户端套接字
    while(1)
    {
	    char buff[SIZE] = {'\0'};
	    int n = recv(clifd, buff, SIZE-1, 0);
	    if(n <= 0) //如果没有数据发送或者客户端断开连接,立即关闭客户端套接字
	    {
            close(clifd);
	        printf("%d is over.\n", clifd);
	        break;
	    }
	    printf("%d : %s\n", clifd, buff);
	    send(clifd, "OK", strlen("OK"), 0);
    }
}

三、客户端

#include<stdio.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h> //htons()
#include<arpa/inet.h> //inet_addr
#include<unistd.h> //close()
#include<string.h> //string()
#define SIZE 128

int main()
{
    
    int listenFd = socket(AF_INET, SOCK_STREAM, 0);
    assert(-1 != listenFd);

    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(6000);
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(listenFd, (struct sockaddr*)&ser, sizeof(ser));
    assert(-1 != res);

    while(1)
    {
	    printf("please input: ");
	    fflush(stdout);
	    char buf[128] = {'\0'};
	    scanf("%s", buf);
	    if(0 == strcmp(buf, "end"))
	    {
	        close(listenFd);
	        break;
	    }
	    send(listenFd, SIZE, SIZE-1, 0);
	
	    char ans[SIZE] = {'\0'};
	    recv(listenFd, ans, SIZE-1, 0);
	    printf("%s\n", ans);
    }
    
    close(listenfd);
    return 0;
}

四、运行结果

猜你喜欢

转载自blog.csdn.net/qq_41822235/article/details/84944700