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。
注意区分,如果是这样的代码:如果A方close掉socket端口后,A方程序中并没有处于阻塞状态的recv,那么这个socket端口一close就是真正关闭了。
代码
本代码服务器和客户端程序写在一起了。具体请看注释。
/*************************************************************************
> File Name: my_chat.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> 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的。
Linux网络编程7——使用TCP实现双方聊天的更多相关文章
- Linux网络编程:基于TCP的程序开发回顾篇《转》
面向连接的TCP程序设计 基于TCP的程序开发分为服务器端和客户端两部分,常见的核心步骤和流程: 其实按照上面这个流程调用系统API确实可以完全实现应用层程序的开发,一点问题没有.可随着时间的推移,你 ...
- 【深入浅出Linux网络编程】 “实践 -- TCP & UDP”
通过上一篇博客的学习,你应该对基于epoll的事件触发机制有所掌握,并且通过阅读sio.c/sio.h应该也学会了如何封装epoll以及如何通过设计令epoll更加实用(用户回调,用户参数). 简单回 ...
- Linux网络编程--wireshark分析TCP包头的格式
摘要: 本文简介了TCP面向连接理论知识,具体讲述了TCP报文各个字段含义.并从Wireshark俘获分组中选取TCP连接建立相关报文段进行分析. 一.概述 TCP是面向连接的可靠传输 ...
- Linux网络编程——浅谈 TCP 三次握手和四次挥手
一.tcp协议格式 二.三次握手 在 TCP/IP 协议中.TCP 协议提供可靠的连接服务,採用三次握手建立一个连接. 第一次握手:建立连接时,client发送 syn 包(tcp协议中syn位置1. ...
- Linux网络编程二、tcp连接API
一.服务端 1.创建套接字: int socket(int domain, int type, int protocol); domain:指定协议族,通常选用AF_INET. type:指定sock ...
- Linux网络编程9——对TCP与UDP的简易封装2.0
具体生成动态库的操作及使用该动态库的操作请参见上篇博文.以下仅仅列出改进版本的代码. 代码 my_socket.h #ifndef __MY_SOCKET_H__ #define __MY_SOCKE ...
- Linux网络编程8——对TCP与UDP的简易封装
引言 每次使用socket通信,都会有很对相似的操作.本文,会对TCP与UDP通信做一简单封装,并生成动态库. 代码 my_socket.h #ifndef __MY_SOCKET_H__ #defi ...
- 【Linux 网络编程】常用TCP/IP网络编程函数
(1)函数socket /**************************************************************** ** 功能:创建一个套接字用于通信 ** 参 ...
- Linux网络编程6——使用TCP实现文件服务器
需求 当客户端连接上服务器后,服务器会将相应文件传输给客户端,实现文件下载. 思路 服务器端,主进程负责listen.循环内,主进程每从任务请求队列中accept出一个请求,就fork出孙子完成文件传 ...
随机推荐
- hdu 1023 Train Problem II
题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=1212 Train Problem II Description As we all know the ...
- P1231: [Usaco2008 Nov]mixup2 混乱的奶牛
这是一道状压DP,首先这道题让我意识到状态是从 1 to (1<<n)-1 的,所以当前加入的某头牛编号是从 0 to n-1 的,所以存储的时候习惯要改一下,这样子做状压DP才会顺一点吧 ...
- android开发 解决启动页空白或黑屏问题
遇到的情况: app启动时进入启动页时出现白屏页,然后大概一秒之后就出现了背景图片. 原因:app启动时加载的是windows背景,之后再加载布局文件的,所以开始的黑屏/白屏就是windows的背景颜 ...
- vs2012 condition_variable notify_one 崩溃
vs2012项目中用到 condition_variable系统方法,程序运行过程过程中偶尔出现notify_one崩溃, 程序运行的服务器系统版本是windows server 2008 R2 SP ...
- 对MVC的理解
摘要:本文主要谈到了对PHP开发中MVC开发模式的理解. 当用户通过url触发命令时,例如url=http://control.blog.sina.com.cn/admin/article/artic ...
- OpenFramework中视频或者图片进行中心旋转、平移、放大、缩小、矫正(本例以视频为准,只给出主要代码)
/********** update mesh部分***********/ for(int i=0;i<4;i++) { mesh[i].clear(); //重要,不加的话,移动视频的四个角 ...
- 使用AzCopy跨账户迁移blob
昨天北美紧急通知要停掉几个开发和测试的订阅,当天必须完成,因为事情比较多,搞得有点我措手不及,但是唯一的遗憾是Azure VM. 因为在上面做了很多东西,很多资料和环境都是做好的,如果被删除掉实在可惜 ...
- proxy server 代理服务器
有时候,我觉得自己需要去搞明白.搞清楚一个概念,帮我打通一下自己的知识体系,或者说,尝试联络起来. 1. 简介 突破自身IP限制,访问国外站点. 访问单位或者团体内部资源. 突破中国电信的IP封锁. ...
- Poj 1050 分类: Translation Mode 2014-04-04 09:31 103人阅读 评论(0) 收藏
To the Max Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 39058 Accepted: 20629 Desc ...
- Http Module 介绍
引言 Http 请求处理流程 和 Http Handler 介绍 这两篇文章里,我们首先了解了Http请求在服务器端的处理流程,随后我们知道Http请求最终会由实现了IHttpHandler接口的类进 ...