Linux网络编程7——使用TCP实现双方聊天

思路

主线程负责发送消息,另一线程负责接收消息。服务端和客户端均是如此。

注意

当A方close掉用于通信的socket端口后,该端口是不会立即关闭的。因为此时可能B方的信息还没send完。因此,此时A方的recv仍旧处于阻塞状态,会最后再等待收一次信息。此时,当B方send一个信息给A后,A方recv到后,A的socket端口就正式关闭了,A的recv返回-1。

此时由于A的socket端口已关闭,因此B得recv返回0。

代码

本代码服务器和客户端程序写在一起了。具体请看注释。

/*************************************************************************
    > File Name: my_chat.c
    > Author: KrisChou
    > Mail:[email protected]
    > Created Time: Thu 28 Aug 2014 11:06:04 PM CST
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <netdb.h>
#define ACTIVE 1
#define PASSIVE 0
#define SERVER_PORT 1314
#define CLIENT_PORT 1350
/* 接收消息通过线程实现 */
void* chat_handler(void* arg)
{
    int fd_client = (int)arg;
    char buf[1024];
    int test;
    while(memset(buf, 0, 1024), (test=recv(fd_client, buf, 1024, 0)) > 0)
    {
        write(1, buf, strlen(buf));
    }
    //用于测试recv返回值
    printf("break child while!\n");
    printf("test = %d \n",test);
    return (void*)0;
    //close(fd_client);
}

int main(int argc,char* argv[])// exe peer_ip
{
    int flag;
    int fd_peer;        /* 对方socket描述符,由acccept返回 */
    if(argc == 1)
    {
        flag = PASSIVE ;
    }else if(argc == 2)
    {
        flag = ACTIVE ;
    }

    /*---------------------------------------socket----------------------------------------*/
    int sfd ; /* sfd为本地socket描述符 */
    sfd = socket(AF_INET, SOCK_STREAM, 0) ;
    if(sfd == -1)
    {
        perror("socket");
        exit(1);
    }

    /*------------ ---------------------------bind -----------------------------------------*/
    char my_hostname[1024]="";
    struct hostent * p ;
    gethostname(my_hostname, 1024); /* 获取本地hostname,以便根据hostname查出ip */
    p = gethostbyname(my_hostname) ;/* 根据hostname,获取指向struct hostent结构体的指针 */ 

    struct sockaddr_in local_addr ; /* 绑定socket端口以及IP的结构体 */
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET ;
    if(flag == ACTIVE)
    {
        local_addr.sin_port = htons(CLIENT_PORT); /* 作为主动方,需要绑定的端口是client_port */
    }else
    {
        local_addr.sin_port = htons(SERVER_PORT); /* 作为服务器,需要绑定的端口server_port */ /*两者均为本机端口*/
    }
    local_addr.sin_addr = *(struct in_addr *)(p ->h_addr_list)[0]; /* 绑定IP地址 */ /* 本地IP */
    if(-1 == bind(sfd, (struct sockaddr *)&local_addr, sizeof(local_addr)))
    {
        perror("bind");
        close(sfd);
        exit(1);
    }

    /*----------------------------------如果是服务器,listen+accept---------------------------------------------- */
    if(flag == PASSIVE)
    {
        if(-1 == listen(sfd, 10))
        {
            perror("listen");
            close(sfd);
            exit(1);
        }
        struct sockaddr_in peer_addr ; /* 存放返回socket描述符方的联系方式的结构体,是传出参数 */
        int len ;
        memset(&peer_addr, 0, sizeof(peer_addr));
        len = sizeof(peer_addr);
        //accept返回对方socket描述符,并且peer_addr与len均为传出参数
        fd_peer = accept(sfd,(struct sockaddr*)&peer_addr,&len);
        printf("%s:%d begin to talk!\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
        close(sfd);//kris
    }else if(flag == ACTIVE) /*-------------如果是主动方,connect -------------------------------------------------*/
    {
        fd_peer = sfd ; //如果是主动方实际上本方就是从sfd通信的。
        /* 主动方要去连对方,需要知道对方IP,通过命令行参数传;也需要知道服务器端口号,程序已经写死 */
        struct sockaddr_in server_addr ;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET ;
        server_addr.sin_port = htons(SERVER_PORT);/*加了htons,如果右边是12345678,传到左边还是12345678;如果不加htons如果右边是12345678,传到左边就变成78563412了 */
        server_addr.sin_addr.s_addr = inet_addr(argv[1]);
        /* connect连不上对方就返回-1,睡一秒后继续连 */
        while( connect(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1)
        {
            sleep(1);
            printf("connecting....\n");
        }
        printf("success ! \n");
    }

        pthread_t thd ;
        pthread_create(&thd, NULL,chat_handler,(void*)fd_peer);
        char msg[1024];
        while(memset(msg, 0, 1024), fgets(msg, 1024, stdin) != NULL)
        {
            send(fd_peer, msg, strlen(msg), 0);
        }
        close(fd_peer);
        //用于测试
        printf("close close close \n");
       pthread_join(thd, NULL);
}

/* 本代码通信是在fd_peer这个socket端口上 */

程序运行退出结果讨论

1. 如果A按ctrl+D(主线程退出while循环,close端口,阻塞在pthread_join上),那么B此时再发送一个消息,在线程中A recv 到并将其打印,此时A的socket端口真正关闭,recv返回-1,退出循环,退出线程,A结束。B由于A的socket端口关闭,线程中的recv返回0,退出循环,退出线程。此时B如果按下ctrl+D,也会退出程序。

2. 一方按下ctrl+D后,另一方马上也按下ctrl+D。则双方陷入僵局。如果一方按下ctrl+c,强行退出程序后,另一方也会跟着退。原因:一方强退,导致socket端口真正关闭,另一方子线程中的recv返回0,退出循环,退出线程。

3. 一方上来就ctrl+c强退。则对方的子线程会退掉,原因同上。此时另一方按ctrl+D就退出程序了。

小结

recv返回值分以下3种情况

1. 大于0。接收到的数据大小。

2. 等于0。对方的socket端口真正关闭。

3. 小于0。出错。其中一种情况是,本方的socket端口已经真正关闭后,还试图从该端口中获取数据。

recv返回值实际上和read是类似的。

发现

send一个空的消息,如下:

send(fd_peer,"",0,0);

对方的recv不会返回0,仍旧阻塞着。

这点和UDP有所不同,在UDP中,一方sendto一个空消息给另一方,对方的recvfrom是返回0的。

时间: 2024-08-27 08:32:00

Linux网络编程7——使用TCP实现双方聊天的相关文章

Linux网络编程--wireshark分析TCP包头的格式

摘要:     本文简介了TCP面向连接理论知识,具体讲述了TCP报文各个字段含义.并从Wireshark俘获分组中选取TCP连接建立相关报文段进行分析. 一.概述     TCP是面向连接的可靠传输协议,两个进程互发数据之前须要建立连接,这里的连接仅仅只是是端系统中分配的一些缓存和状态变量,中间的分组交换机不维护不论什么连接状态信息. 连接建立整个步骤例如以下(即三次握手协议): 首先,客户机发送一个特殊的TCP报文段: 其次,server用还有一个特殊的TCP报文段来响应: 最后,客户机再用

Linux网络编程——浅谈 TCP 三次握手和四次挥手

一.tcp协议格式 二.三次握手 在 TCP/IP 协议中.TCP 协议提供可靠的连接服务,採用三次握手建立一个连接. 第一次握手:建立连接时,client发送 syn 包(tcp协议中syn位置1.序号为J)到server,并进入 SYN_SEND 状态.等待server确认: 第二次握手:server收到 syn 包,必须确认客户的 SYN,同一时候自己也发送一个 SYN 包,即 SYN+ACK包(tcp协议中syn位置1,ack位置1.序号K,确定序号为J+1),此时server进入 SY

Linux网络编程二、tcp连接API

一.服务端 1.创建套接字: int socket(int domain, int type, int protocol); domain:指定协议族,通常选用AF_INET. type:指定socket类型,TCP通信下使用SOCK_STREAM. protocol:指定协议,通常为0. 返回值:成功则返回新socket的文件描述符,失败返回-1. 头文件:sys/socket.h     sys/types.h 2.绑定套接字 int bind(int sockfd, struct sock

【Linux 网络编程】常用TCP/IP网络编程函数

(1)函数socket 1 /**************************************************************** 2 ** 功能:创建一个套接字用于通信 3 ** 参数:domain 指定通信协议族 4 ** type 指定socket类型,流式套接字 SOCK_STREAM 5 ** 数据报套接字 SOCKDGRAM 6 ** 原始套接字 SOCKRAW 7 ** protocol 协议类型 (习惯上填写0) 8 ** 返回值:成功返回非负整数,它

Linux网络编程8&mdash;&mdash;对TCP与UDP的简易封装

引言 每次使用socket通信,都会有很对相似的操作.本文,会对TCP与UDP通信做一简单封装,并生成动态库. 代码 my_socket.h #ifndef __MY_SOCKET_H__ #define __MY_SOCKET_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #in

Linux网络编程9&mdash;&mdash;对TCP与UDP的简易封装2.0

具体生成动态库的操作及使用该动态库的操作请参见上篇博文.以下仅仅列出改进版本的代码. 代码 my_socket.h #ifndef __MY_SOCKET_H__ #define __MY_SOCKET_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <sys

Linux网络编程6&mdash;&mdash;使用TCP实现文件服务器

需求 当客户端连接上服务器后,服务器会将相应文件传输给客户端,实现文件下载. 思路 服务器端,主进程负责listen.循环内,主进程每从任务请求队列中accept出一个请求,就fork出孙子完成文件传输.注意:如果只是fork出儿子,那么主进程就得wait儿子,这样的话,只有当给一个客户端传完文件后才能下一个. 代码 server端 /************************************************************************* > File

Linux网络编程10&mdash;&mdash;使用UDP实现五子棋对战

思路 1. 通信 为了同步双方的棋盘,每当一方在棋盘上落子之后,都需要发送给对方一个msg消息,让对方知道落子位置.msg结构体如下: /* 用于发给对方的信息 */ typedef struct tag_msg { int msg_type; /* 悔棋? */ int msg_color; int msg_row; int msg_col; }MSG, *pMSG; 2. 悔棋 用链表头插法来模拟栈,链表记录了双方下子的轨迹.结构如下: /* 记录每一步的轨迹 */ typedef stru

linux网络编程笔记——TCP

1.TCP和UDP TCP是长连接像持续的打电话,UDP是短消息更像是发短信.TCP需要消耗相对较多的资源,但是传输质量有保障,UDP本身是不会考虑传输质量的问题. 2.网络传输内容 我习惯的做法是直接通过TCP传送结构体,当然前提是收发两端都在程序里对目标结构体有充分的定义.特别说明的一点是,要小心收发两端处理器的大小端问题!而且传输信息头里必须包含长度信息,而且通用的是大端.但是,这里的长度和结构体,我选择用小端进行传输. 3.TCPserver实现 参考了别人多线程的回调写法,看起来不错.