概述

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP 具有以下特点:

1)电话系统服务模式的抽象

2)每一次完整的数据传输都要经过建立连接、使用连接、终止连接的过程

3)可靠、出错重传、且每收到一个数据都要给出相应的确认,保证数据传输的可靠性

TCP 编程的 C/S 架构

基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下:

TCP 客户端编程

对于 TCP 客户端编程流程,有点类似于打电话过程:找个可以通话的手机(socket() ) -> 拨通对方号码并确定对方是自己要找的人( connect() ) -> 主动聊天( send() 或 write() )-> 或者,接收对方的回话( recv() 或 read() )-> 通信结束后,双方说再见挂电话(close() )。
所需头文件:#include <sys/socket.h>
int socket(int family,int type,int protocol);
功能:

创建一个用于网络通信的 socket套接字(描述符),详细用法,请看《套接字的介绍》

参数:
family:本示例写 AF_INET,代表 IPv4
type:本示例写 SOCK_STREAM,代表 TCP 数据流
protocol:这里写 0,设为 0 表示使用默认协议
返回值:
成功:套接字
失败 < 0 

int connect( int sockfd, const struct sockaddr *addr, socklen_t len );

功能:

主动跟服务器建立连接,有点类似于,我们给别人电话,主动拨对方的电话号码,具体是怎么一个过程,请《connect()、listen()和accept()三者之间的关系》。

参数:

sockfd:socket()返回的套接字

addr:连接的服务器地址结构

len:地址结构体长度

返回值:

成功:0

失败:-1

connect() 函数相当于拨号码,只有拨通号码并且确定对方是自己要找的人(三次握手)才能进行下一步的通信。

ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);

功能:

发送数据,最后一个参数为 0 时,可以用 write() 替代( send 等同于 write )。注意:不能用 TCP 协议发送 0 长度的数据包。假如,数据没有发送成功,内核会自动重发。

参数:

sockfd: 已建立连接的套接字

buf: 发送数据的地址

nbytes: 发送缓数据的大小(以字节为单位)

flags: 套接字标志(常为 0)

返回值:

成功:成功发送的字节数

失败 < 0

虚拟机中Redhat5.5的 TCP 客户端程序代码:

#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>#define PORT 10086
#define SIZE 128/***服务端:*    a. 创建一个套字*    b. 初始化结构体*    c. 绑定*    d. 监听 设置监听队列的大小*    e. 接受客户端的连接*    f. 读写操作**/int main(void)
{int sockfd = 0;int newfd = 0;int ret = -1;char buf[SIZE];struct sockaddr_in sockaddr;struct sockaddr_in fromaddr;socklen_t len = sizeof(fromaddr);//a. 创建套接子sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("socket"); goto err0;}//b. 初始化结构体memset(&sockaddr, 0, sizeof(sockaddr));sockaddr.sin_family = AF_INET; //ipv4sockaddr.sin_port = htons(PORT);sockaddr.sin_addr.s_addr = inet_addr("172.16.1.88");//绑定ret = bind(sockfd, (void*)&sockaddr, sizeof(sockaddr));if (-1 == ret){perror("bind"); goto err1;}//监听ret = listen(sockfd, 10);if (-1 == ret){perror("listen"); goto err1;}printf("server is waiting the client incomming....\n");//阻塞 接受客户端连接newfd = accept(sockfd, (void*)&fromaddr, &len);if (-1 == newfd){perror("accept"); goto err1;}//客户端的信息printf("from client  ip: %s port: %d\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port));//读写操作while(1){memset(buf, 0, SIZE);         //ret = write(newfd, "hello world", 11); ret = send(newfd, "hello world", 11, 0);if (ret <= 0)break;printf("send  %d bytes\n", ret);sleep(1);}close(sockfd);return 0;
err1:close(sockfd);
err0:return -1;
}

运行结果如下:

对于客户端,也是可以接收数据,前提为,客户端先给服务器发送数据。

ssize_t recv(int sockfd, void *buf,  size_t nbytes, int flags);

功能:

接收网络数据,默认的情况下,如果没有接收到数据,这个函数会阻塞,直到有数据到来。

参数:

sockfd:套接字

buf:接收网络数据的缓冲区的地址

nbytes:接收缓冲区的大小(以字节为单位)

flags:套接字标志(常为 0 )

返回值:

成功:成功接收的字节数

失败 < 0

测试代码如下:

#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>#define PORT 10086
#define SIZE 128/***客户端:*    a. 创建一个套接子*    b. 初始化结构体*    e. 链接服务器*    f. 读写操作**/int main(void)
{int sockfd = 0;int newfd = 0;int ret = -1;char buf[SIZE];struct sockaddr_in sockaddr;socklen_t len = sizeof(sockaddr);//a. 创建套接子sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("socket"); goto err0;}//b. 初始化结构体 使用服务器相关信息memset(&sockaddr, 0, sizeof(sockaddr));sockaddr.sin_family = AF_INET; //ipv4sockaddr.sin_port = htons(PORT);sockaddr.sin_addr.s_addr = inet_addr("172.16.1.88");//c. 连接服务器ret = connect(sockfd, (void*)&sockaddr, len);if (-1 == ret){perror("connect"); goto err1;}printf("connect to server successfully....\n");//读写操作while(1){memset(buf, 0, SIZE);         //ret = read(sockfd, buf, SIZE);ret = recv(sockfd, buf, SIZE, 0);if (ret <= 0)break;buf[ret] = 0;printf("from server: %s\n", buf);}close(sockfd);return 0;
err1:close(sockfd);
err0:return -1;
}

运行结果如下:

TCP 服务器编程

做为 TCP 服务器需要具备的条件呢?

  • 具备一个可以确知的地址( bind() ):相当于我们要明确知道移动客服的号码,才能给他们电话;
  • 让操作系统知道是一个服务器,而不是客户端( listen() ):相当于移动的客服,他们主要的职责是被动接听用户电话,而不是主动打电话骚扰用户;
  • 等待连接的到来( accept() ):移动客服时刻等待着,来一个客户接听一个。

接收端使用 bind() 函数,来完成地址结构与socket 套接字的绑定,这样 ip、port 就固定了,发送端即可发送数据给有明确地址( ip+port ) 的接收端。

对于 TCP 服务器编程流程,有点类似于接电话过程:找个可以通话的手机(socket() ) -> 插上电话卡固定一个号码( bind() ) -> 职责为被动接听,给手机设置一个铃声来监听是否有来电( listen() ) -> 有来电,确定双方的关系后,才真正接通不挂电话( accept() ) -> 接听对方的诉说( recv() ) -> 适当给些回话( send() )-> 通信结束后,双方说再见挂电话( close() )。

int bind( int sockfd, const struct sockaddr *myaddr,socklen_t addrlen );

功能:

将本地协议地址与 sockfd 绑定,这样 ip、port 就固定了

参数:

sockfd:socket 套接字

myaddr: 指向特定协议的地址结构指针

addrlen:该地址结构的长度

返回值:

成功:返回 0

失败:-1

使用实例如下:

// 本地网络地址
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));   // 清空结构体内容
my_addr.sin_family = AF_INET;  // ipv4
my_addr.sin_port   = htons(port);  // 端口转换
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定网卡所有ip地址,INADDR_ANY为通配地址,值为0printf("Binding server to port %d\n", port);
int err_log;
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); // 绑定
if(err_log != 0)
{perror("bind");close(sockfd);        exit(-1);
}

int listen(int sockfd, int backlog);

功能:

将套接字由主动修改为被动,使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接。更详细说明,请看《connect()、listen()和accept()三者的关系》。

参数:

sockfd: socket监听套接字

backlog:连接队列的长度

返回值:

成功:返回0

失败:其他

int accept(  int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen );

功能:

从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)。更详细说明,请看《connect()、listen()和accept()三者的关系》。

参数:

sockfd: socket监听套接字

cliaddr: 用于存放客户端套接字地址结构

addrlen:套接字地址结构体长度的地址

返回值:

成功:已连接套接字。注意:返回的是一个已连接套接字,这个套接字代表当前这个连接

失败:< 0

Redhat 中的服务器代码如下:

#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>#define PORT 10086
#define SIZE 128/***服务端:*    a. 创建一个套字*    b. 初始化结构体*    c. 绑定*    d. 监听 设置监听队列的大小*    e. 接受客户端的连接*    f. 读写操作**/int main(void)
{int sockfd = 0;int newfd = 0;int ret = -1;char buf[SIZE];struct sockaddr_in sockaddr;struct sockaddr_in fromaddr;socklen_t len = sizeof(fromaddr);//a. 创建套接子sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("socket"); goto err0;}//b. 初始化结构体memset(&sockaddr, 0, sizeof(sockaddr));sockaddr.sin_family = AF_INET; //ipv4sockaddr.sin_port = htons(PORT);sockaddr.sin_addr.s_addr = inet_addr("172.16.1.88");//绑定ret = bind(sockfd, (void*)&sockaddr, sizeof(sockaddr));if (-1 == ret){perror("bind"); goto err1;}//监听ret = listen(sockfd, 10);if (-1 == ret){perror("listen"); goto err1;}printf("server is waiting the client incomming....\n");//阻塞 接受客户端连接newfd = accept(sockfd, (void*)&fromaddr, &len);if (-1 == newfd){perror("accept"); goto err1;}//客户端的信息printf("from client  ip: %s port: %d\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port));//读写操作while(1){memset(buf, 0, SIZE);         //ret = write(newfd, "hello world", 11); ret = send(newfd, "hello world", 11, 0);if (ret <= 0)break;printf("send  %d bytes\n", ret);sleep(1);}close(sockfd);return 0;
err1:close(sockfd);
err0:return -1;
}

Windows 的网络调试助手作为 TCP 客户端,给 ubuntu 中的服务器发送数据,运行结果如下:

关闭连接:close()

使用 close() 函数即可关闭套接字,关闭一个代表已连接套接字将导致另一端接收到一个 0 长度的数据包,详情请看《 TCP 四次挥手》。

做服务器时

  • 关闭监听套接字( socket()和listen()之后的套接字 )将导致服务器无法接收新的连接,但不会影响已经建立的连接;
  • 关闭 accept()返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听( socket()和listen()之后的套接字 )。

做客户端时

关闭连接就是关闭连接,不意味着其他。

如果客户端和服务器已经连接成功的前提下,通常的情况下,先关闭客户端,再关闭服务器,如果是先关闭服务器,立马启动服务器是,服务器绑定的端口不会立马释放(如下图),要过 1 分钟左右才会释放,为什么会这样的呢?请看《 TCP 四次挥手》。有没有方法让服务器每次启动都能立即成功?请看《端口复用》。

【Linux网络编程】TCP相关推荐

  1. Linux网络编程——tcp并发服务器(poll实现)

    https://blog.csdn.net/lianghe_work/article/details/46535859 想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程--I/ ...

  2. Linux 网络编程 TCP

    (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍 客户端和服务端         网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客 ...

  3. Linux 网络编程——TCP编程

    概述 TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议. TCP 具有以下特点: 1)电话系统服务模式的抽象 2) ...

  4. linux网络编程tcp和udp基本函数调用过程及如何选择

    1. socket编程 1.1 概述 TCP是TCP/IP体系中面向连接的传输层协议,它提供全双工和可靠交付的服务.它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号.确认重传.滑动窗口等 ...

  5. Linux网络编程——tcp并发服务器(多进程)

    https://blog.csdn.net/lianghe_work/article/details/46503895 一.tcp并发服务器概述 一个好的服务器,一般都是并发服务器(同一时刻可以响应多 ...

  6. Linux网络编程--TCP中的三次握手和四次挥手

    服务器编程和客户端编程的大致流程如下: 三次握手是在客户端中的connect中完成的,具体如下: 那么上述说到的SYN     ACK这些是什么东西呢? 上述的截图取自<Linux高性能服务器编 ...

  7. linux网络编程-----TCP连接及相关问题

    c/s模型在建立连接时的流程如下 //服务器端 int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr; bz ...

  8. Linux 网络编程——TCP/IP 数据包格式解析

    图中括号中的数字代表的是当前域所占的空间大小,单位是bit位. 黄色的是数据链路层的头部,一共14字节 绿色的部分是IP头部,一般是20字节 紫色部分是TCP头部,一般是20字节 最内部的是数据包内容 ...

  9. Linux网络编程(TCP)

    TCP service.c 服务端 #include <stdlib.h> #include <sys/types.h> #include <stdio.h> #i ...

  10. Linux网络编程 | TCP状态转换【2MSL】

    文章目录 一.TCP状态转换 1.半关闭 2.2MSL 一.TCP状态转换 CLOSED:表示初始状态. LISTEN:表示服务器端的某个SOCKET处于监听状态,可连接. SYN_SENT:表示客户 ...

最新文章

  1. STL中istream_iterator和ostream_iterator的基本用法
  2. Java中 EvenQueue.invokeLater用法
  3. Map-Reduce编程模型gif图片解释
  4. 条款20 :宁以pass-by-reference-to-const 替换pass-by-value
  5. java 观察者模式_Java技术干货分享:深入理解观察者模式原理与技术
  6. 请对比html与css的异同,css3与css2的区别是什么?
  7. 阿里云Https部署网站
  8. 一文读懂什么是P问题、NP问题和NPC问题
  9. Beta冲刺——星期三
  10. C++相对路径下新建文件夹
  11. MATLAB视频与图片之间的相互转换
  12. itextPdf pdf加水印
  13. 毕设——基于SpringBoot的电影荐评系统
  14. 六、Prometheus+Grafana搭建监控系统
  15. PyTorch 轻松节省显存的小技巧
  16. virt-install命令详解
  17. 基于SSM的美容院管理系统
  18. 如何直观理解AUC评价指标?
  19. JNI/NDK开发指南(十一)——JNI异常处理
  20. web页面中如何唤起打开APP

热门文章

  1. python测试需要学什么_从手工测试到自动化测试需要学什么?
  2. 中考计算机考试作文,中考理化实验计算机考试作文
  3. Java黑皮书课后题第6章:**6.24(显示当前日期和时间)程序清单2-7显示当前时间。改进这个例子,显示当前的日期和时间。程序清单6-12中的日历例子可以提供一些如何提供如何求年月日的思路
  4. 【mysql】配置 选项文件
  5. @Transactional注解事务不回滚不起作用无效
  6. Python 案例001 (有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数)...
  7. AndroidOS体系结构
  8. Server.UrlEncode、HttpUtility.UrlDecode不同编码
  9. 十八个超经典故事 绝对不会后悔
  10. 究竟什么能使得生活变得圆满?