基于tcp协议的网络程序流程图如下:

tcp协议网络程序流程图

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态

客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答

服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

一. 协议流程分析

建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

在学习socket API时要注意应用程序和TCP协议层是如何交互的: *应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 *应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

二. 简单例子(单线程实现客户端与服务端的简单通信)

实现功能: 客户端连接服务端后,不断从控制台读取字符串,发给服务端,服务端接收后则在控制台界面输出

/*

* @Author: D-lyw

* @Date: 2018-10-25 00:48:44

* @Last Modified by: D-lyw

* @Last Modified time: 2018-11-16 12:36:34

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define SERVADDR_PORT 8800

const char *LOCALIP = "127.0.0.1";

int main(int argc, char const *argv[])

{

// 定义变量存储生成或接收的套接字描述符

int listenfd, recvfd;

// 定义一个数据结构用来存储套接字的协议,ip,端口等地址结构信息

struct sockaddr_in servaddr, clientaddr;

// 定义接收的套接字的数据结构的大小

unsigned int cliaddr_len, recvLen;

char recvBuf[1024];

//创建用于帧听的套接字

listenfd = socket(AF_INET, SOCK_STREAM, 0);

// 给套接字数据结构赋值,指定ip地址和端口号

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(SERVADDR_PORT);

servaddr.sin_addr.s_addr = inet_addr(LOCALIP);

// 绑定套接字

if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){

fprintf(stderr, "绑定套接字失败,%s\n", strerror(errno));

exit(errno);

}

// 监听请求

if(listen(listenfd, 10) == -1){

fprintf(stderr, "绑定套接字失败,%s\n", strerror(errno));

exit(errno);

}

cliaddr_len = sizeof(struct sockaddr);

// 等待连接请求

while (1){

// 接受由客户机进程调用connet函数发出的连接请求

recvfd = accept(listenfd, (struct sockaddr *)&clientaddr, &cliaddr_len);

printf("接收到请求套接字描述符: %d\n", recvfd);

while(1){

// 在已建立连接的套接字上接收数据

if((recvLen = recv(recvfd, recvBuf, 1024, 0)) == -1){

fprintf(stderr,"接收数据错误, %s\n",strerror(errno));

}

printf("%s", recvBuf);

}

}

close(recvfd);

return 0;

}

/*

* @Author: D-lyw

* @Date: 2018-10-26 14:06:32

* @Last Modified by: D-lyw

* @Last Modified time: 2018-11-16 12:34:08

* @name tcp_client.c

* @descripe 实现最基本的创建套接字, 填充客户端信息,connet连接服务端, 可连续向服务端发送消息

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

extern int errno;

#define SERVERPORT 8800

int main(int argc, char const *argv[])

{

// 定义变量存储本地套接字描述符

int clifd;

// 设置本地ip地址

const char serverIp[] = "127.0.0.1";

// 定义套接字结构存储套接字的ip,port等信息

struct sockaddr_in cliaddr_in;

// 定义发送,接收缓冲区大小

char sendBuf[1024], recvBuf[1024];

// 创建套接字

if((clifd = socket(AF_INET, SOCK_STREAM, 0)) == -1){

fprintf(stderr, "创建套接字失败,%s\n", strerror(errno));

exit(errno);

}

// 填充 服务器端结构体信息

cliaddr_in.sin_family = AF_INET;

cliaddr_in.sin_addr.s_addr = inet_addr(serverIp);

cliaddr_in.sin_port = htons(SERVERPORT);

// 请求连接服务器进程

if(connect(clifd, (struct sockaddr *)&cliaddr_in, sizeof(struct sockaddr)) == -1){

fprintf(stderr,"请求连接服务器失败, %s\n", strerror(errno));

exit(errno);

}

strcpy(sendBuf, "hi,hi, severs!\n");

// 发送打招呼消息

if(send(clifd, sendBuf, 1024, 0) == -1){

fprintf(stderr, "send message error:(, %s\n", strerror(errno));

exit(errno);

}

// 阻塞等待输入,发送消息

while(1){

fgets(sendBuf, 1024, stdin);

if(send(clifd, sendBuf, 1024, 0) == -1){

fprintf(stderr, "send message error:(, %s\n", strerror(errno));

}

}

close(clifd);

return 0;

}

三. 聊天室功能实现(多线程)

服务器端代码

/*

* @Author: D-lyw

* @Date: 2018-11-22 20:37:05

* @Last Modified by: D-lyw

* @Last Modified time: 2018-11-23 00:19:42

* @Describe Chating Room Coded by linux c .

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

const unsigned short LOCALPORT = 3003;

const char *LOCALIP = "127.0.0.1";

#define MAXMSGSIZE 1024*5

#define MAXCONNECT 100

char sendbuf[MAXMSGSIZE];

char recvbuf[MAXMSGSIZE];

struct msgHdr{

int fd; // 套接字描述符

ushort tip; // 0 进入聊天室, 1 离开聊天室 2 发送消息

ushort onLineNum; // 在线人数

};

// 线程描述符

pthread_t Precv;

pthread_t Psend;

int clientFdarray[MAXCONNECT];

struct msgHdr *sendMsgHdr, *recvMsgHdr;

int onLineNum = 0; // 在线人数

void rmFd(int dealfd){

for(int i = 0; i < (onLineNum+1); i++){

if(clientFdarray[i] == dealfd){

for(; i < onLineNum; i++){

clientFdarray[i] = clientFdarray[i+1];

}

printf("当前所有用户:\n");

for(int j = 0; j < onLineNum; j++){

printf(" 用户:%d\n", clientFdarray[j]);

}

}

}

}

// 服务器发送消息线程

void sendToClient(char *buf, int dealfd){

for(int j = 0; j < onLineNum; j++){

if(clientFdarray[j] == dealfd){

continue;

}

if(send(clientFdarray[j], buf, MAXMSGSIZE, 0) == -1){

fprintf(stderr, "%s\n", strerror(errno));

}

}

bzero(buf, MAXMSGSIZE);

}

// 接收客户端消息线程

void *recvMsg(void *recvfd){

int dealfd = *(int *)recvfd;

while(1){

if(recv(dealfd, recvbuf, MAXMSGSIZE, 0) == -1){

fprintf(stderr, "Receive msg err: %s\n", strerror(errno));

}

recvMsgHdr = (struct msgHdr *)recvbuf;

recvMsgHdr->fd = dealfd;

if(recvMsgHdr->tip == 1){

onLineNum--;

recvMsgHdr->onLineNum = onLineNum;

// 将离开的套接字描述符移开在线列表数组

rmFd(dealfd);

printf("用户:%d 离开了聊天室\n", dealfd);

sendToClient(recvbuf, dealfd);

close(dealfd);

return NULL;

}

// 将此用户的消息发给其他用户

sendToClient(recvbuf, dealfd);

}

close(dealfd);

return NULL;

}

int main(int argc, char const *argv[])

{

int serverfd, recvfd;

socklen_t sockleng;

struct sockaddr_in serveraddr, clientaddr;

pid_t childid;

int perrno;

if( (serverfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1){

fprintf(stderr, "创建服务器套接字错误, %s\n", strerror(errno));

exit(0);

}

serveraddr.sin_family = AF_INET;

serveraddr.sin_port = htons(LOCALPORT);

serveraddr.sin_addr.s_addr = inet_addr(LOCALIP);

if(bind(serverfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1){

fprintf(stderr, "绑定套接字错误, %s\n", strerror(errno));

exit(0);

}

if(listen(serverfd, 100) == -1){

fprintf(stderr, "监听套接字错误, %s\n", strerror(errno));

}

printf("\nListening at %d port, wating connection.....\n", LOCALPORT);

while(1){

if((recvfd = accept(serverfd, (struct sockaddr *)&clientaddr, &sockleng)) == 0){

fprintf(stderr, "连接错误, %s\n", strerror(errno));

continue;

}

// 将该套接字描述符保存进数组

clientFdarray[onLineNum++] = recvfd;

printf("客户端套接字:%d 已开启\n", recvfd);

sendMsgHdr = (struct msgHdr *)sendbuf;

sendMsgHdr->fd = recvfd;

sendMsgHdr->tip = 0;

sendMsgHdr->onLineNum = onLineNum;

// 当有用户加入时,通知聊天室中的所有人

for(int j = 0; j < onLineNum; j++){

if(send(clientFdarray[j], sendbuf, MAXMSGSIZE, 0) == -1){

fprintf(stderr, "%s\n", strerror(errno));

}

}

bzero(sendbuf, MAXMSGSIZE);

// 创建接收用户消息处理线程

if((perrno = pthread_create(&Precv, NULL, recvMsg, &recvfd)) != 0){

fprintf(stderr, "创建子消息接收线程失败, %s\n", strerror(perrno));

exit(perrno);

}

}

close(serverfd);

return 0;

}

客户端代码

/*

* @Author: D-lyw

* @Date: 2018-11-22 21:47:58

* @Last Modified by: D-lyw

* @Last Modified time: 2018-11-23 00:37:01

*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

extern int errno;

#define MAXSIZE 1024*5

#define SERVER_PORT 3003

const char *SERVERIP = "127.0.0.1";

// const char *SERVERIP = "120.78.156.5";

char sendbuf[MAXSIZE];

char recvbuf[MAXSIZE];

struct msgHdr{

int fd; // 套接字描述符

ushort tip; // 0 进入聊天室, 1 离开聊天室 2 发送消息

ushort onLineNum; // 在线人数

};

int sockfd;

struct msgHdr *mySendMsgHdr, *myRecvMsgHdr;

void *sendMsg(void *msg){

while(1){

mySendMsgHdr = (struct msgHdr *)sendbuf;

mySendMsgHdr->fd = sockfd;

fgets(sendbuf + sizeof(struct msgHdr), MAXSIZE - sizeof(struct msgHdr), stdin);

if(strncmp(sendbuf + sizeof(struct msgHdr), "end", 3) == 0){ // 用户离开聊天室

mySendMsgHdr->tip = 1;

if(send(sockfd, sendbuf, MAXSIZE, 0) == -1){

fprintf(stderr, "%s\n", strerror(errno));

}

close(sockfd);

exit(0);

}else{

mySendMsgHdr->tip = 2; // 用户发送数据

}

if(send(sockfd, sendbuf, MAXSIZE, 0) == -1){

fprintf(stderr, "%s\n", strerror(errno));

}

bzero(sendbuf, MAXSIZE);

}

return NULL;

}

int main(int argc, char const *argv[])

{

ssize_t sendLen;

struct sockaddr_in seraddr, recvaddr;

pthread_t Psend;

// 创建一个客户端的套接字

if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){

fprintf(stderr, "%s\n", strerror(errno));

exit(errno);

}

bzero(&seraddr, sizeof(struct sockaddr_in));

// 服务器端地址信息

seraddr.sin_family = AF_INET;

seraddr.sin_addr.s_addr = inet_addr(SERVERIP);

seraddr.sin_port = htons(SERVER_PORT);

// 请求连接服务器进程

if(connect(sockfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr)) == -1){

fprintf(stderr,"请求连接服务器失败, %s\n", strerror(errno));

exit(errno);

}

printf("--------Successful connect to %s:%d--------\n", inet_ntoa(seraddr.sin_addr), ntohs(seraddr.sin_port));

// 新建线程发送消息

pthread_create(&Psend, NULL, sendMsg, NULL);

// 接收其他用户消息

while(1){

// 清空缓存区

bzero(recvbuf, MAXSIZE);

if(recv(sockfd, recvbuf, MAXSIZE, 0) == -1){

fprintf(stderr, "%s\n", strerror(errno));

}

myRecvMsgHdr = (struct msgHdr *)recvbuf;

if(myRecvMsgHdr->tip == 0){

fprintf(stdout, " **用户 %d 加入聊天室 当前用户: %d 人** \n", myRecvMsgHdr->fd, myRecvMsgHdr->onLineNum);

}else if(myRecvMsgHdr->tip == 1){

printf(" **用户 %d 离开聊天室 当前用户: %d 人** \n", myRecvMsgHdr->fd, myRecvMsgHdr->onLineNum);

}else if(myRecvMsgHdr->tip == 2){

fprintf(stdout, "#%d> %s\n", myRecvMsgHdr->fd, recvbuf+sizeof(struct msgHdr));

}

}

return 0;

}

linux tcp 服务器 c,Linux网络编程篇之Tcp协议介绍, C/S通信及聊天室实现相关推荐

  1. Linux网络编程篇之ICMP协议分析及ping程序实现

    Linux网络编程系列: Linux网络编程篇之Socket编程预备知识 Linux网络编程篇之TCP协议分析及聊天室功能实现 如果对Linux网络编程,对socket通信不是太清楚的同学,强烈推荐看 ...

  2. java tcp怎么拆包_Java网络编程基础之TCP粘包拆包

    TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想象河里的流水,他们是连成一片的,其间并没有分界线.TCP底层并不了解上层业务数据的具体含义,他会根据TCP缓冲区的实 ...

  3. Linux C/C++ 开发(学习笔记十一 ):TCP服务器(并发网络网络编程 一请求一线程)

    Linux C/C++ 开发(学习笔记十一 ):TCP服务器(并发网络网络编程 一请求一线程) 一.TCP服务器(一请求一线程) 的原理 二.完整代码 三.测试 四.补充 一.TCP服务器(一请求一线 ...

  4. 【Linux】网络编程三:TCP通信和UDP通信介绍及代码编写

    参考连接:https://www.nowcoder.com/study/live/504/2/16. [Linux]网络编程一:网络结构模式.MAC/IP/端口.网络模型.协议及网络通信过程简单介绍 ...

  5. Linux网络编程---详解TCP

    Linux网络编程---详解TCP的三次握手和四次挥手_shanghx_123的博客-CSDN博客_tcp的协议数据单元被称为 TCP协议详解(TCP报文.三次握手.四次挥手.TIME_WAIT状态. ...

  6. linux下C语言socket网络编程简例

    转自博文:http://blog.csdn.net/kikilizhm/article/details/7858405 在练习写网络编程时,该例给了我帮助,在写服务器时,我把while逻辑位置想法错了 ...

  7. Linux多线程、多进程、网络编程常见问题

    Linux多进程.多线程.网络编程常见问题 入门 1.GCC的工作流程? 工作流程: 预处理.编译.汇编.链接 具体流程如下: 2.gcc常用的参数选项 3.Makefile介绍 3.1.Makefi ...

  8. 客户和服务器之间响应的序列,网络编程-第五讲-TCP客户-服务器程序例子.pdf-原创力文档...

    网络编程 第五讲TCP客户-服务器程序例子 多进程并发服务器基本架构 pid_t pid; int listenfd, connfd; listenfd = Socket( ... ); /* fil ...

  9. 高等学校计算机科学与技术教材:tcp/ip网络编程技术基础,TCP/IP网络编程技术基础...

    TCP/IP网络编程技术基础 语音 编辑 锁定 讨论 上传视频 <TCP/IP网络编程技术基础>是2012年北京交通大学出版社出版的图书,作者是王雷. 书    名 TCP/IP网络编程技 ...

最新文章

  1. ssh中c3p0连接mysql_JSP+SSH+Mysql+C3P0实现的传智播客网上商城
  2. docsify——一个神奇的文档站点生成器
  3. 容器化分布式日志组件ExceptionLess的Angular前端UI
  4. html单行元素居中显示,多行元素居左显示
  5. 章国锋:视觉SLAM最新观点分享
  6. 2021年微信小程序点餐系统功能模板搭建
  7. php写登录的视频,PHP cookie实现记录用户登陆信息的方法(图文+视频)
  8. 推荐一个 github 项目 spider163,抓取网络数据,歌曲评论等数据
  9. 代码“可读性”到底有多重要?
  10. 如何查询电脑系统和服务器地址,如何查询电脑系统和服务器地址
  11. 谷歌公布云游戏平台「Stadia」 预计2019年上线
  12. vi/vim简介及使用教程
  13. 老子道德经81章全文及解释
  14. java和javac版本不一致(三种解决方法)
  15. 中考可以用计算机,中考计算机考试内容·中考信息技术要考哪些项目?
  16. 2022年全球与中国辐射屏蔽墙行业产销需求与投资预测分析报告
  17. C语言中使用printf()打印漂亮的颜色字体
  18. sitemap.xml格式实例说明
  19. [Paper Reading-3d] AFDetV2: Real-Time Anchor-Free Single-Stage 3D Detection with IoU-Awareness
  20. Vue中通过el-upload组件实现上传前预览本地图片

热门文章

  1. php中const的意思,php – 在const中使用const
  2. mysql临时关闭查询日志_mysql故障排错临时打开通用日志和慢查询日志
  3. mysql怎么避免联合查询_mysql-联合查询,连接查询
  4. python单链表实现具体例子_python中单链表的实现
  5. mysql建用户之后取消drop库权限
  6. linux有个进程有问题_第五十五章、linux下进程的基本知识
  7. html 标签面板,HTML 标签大全及属性
  8. 永久linux修改内核打印级别,终端下更改printk打印级别
  9. Java Excel(jxl)学习笔记
  10. 基于JAVA+SpringMVC+MYSQL的鲜花销售平台