TCP套接字编程
一.套接字(socket)函数
图1给出了在一个TCP客户与服务器通信的流程。服务器首先启动,稍后某个客户启动,它试图连接到服务器。假设客户给服务器发送一个请求,服务器处理该请求,并且给客户发回一个相应。这个过程一直持续下去,知道客户关闭连接的客户端,从而给服务器发送一个EOF(文件结束)通知为止。服务器接着也关闭连接的服务器端,然手结束运行或者等待新的客户连接。

图1 客户/服务器程序的套接字函数
1.socket函数
#include<sys/socket.h>
int socket(int family,int type,int protocol);
为了执行一个I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型。其中family参数指明协议族,见表1;type参数指明套接字类型,见表2;protocol参数指明协议,见表3。
| family | 说明 |
| AF_INET | IPv4协议 |
| AF_INET6 | IPv6协议 |
| AF_LOCAL | Unix域协议 |
| AF_ROUTE | 路由套接字 |
| AF_KEY | 密钥套接字 |
表1 socket函数family常值
| type | 说明 |
| SOCK_STREAM | 字节流套接字 |
| SOCK_DGRAM | 数据报套接字 |
| SOCK_SEQPACKET | 有序分组套接字 |
| SOCK_RAW | 原始套接字 |
表2 socket函数type常值
| protocol | 说明 |
| IPPROTO_TCP | TCP传输协议 |
| IPPROTO_UDP | UDP传输协议 |
| IPPROTO_SCTP | SCTP传输协议 |
表3 socket函数AF_INET或AF_INET6的protocol常值
socket函数在成功时返回一个套接字描述符(sockfd),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
2.connect函数
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *seraddr,socklen_t addrlen);
TCP客户用connect函数来与服务器建立连接。第一个参数是socket函数返回的套接字描述符,第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。下面给出IPv4和IPv6套接字的地址结构,帮助理解:
//IPv4套接字地址结构
struct in_addr {
int_addr_t s_addr; /* 32-bit IPv4 address */
/* network byte order */
}; struct sockaddr_in {
uint8_t sin_len; /* len of structure */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit TCP or UDP port number */
/* network byte order */
struct in_addr sin_addr; /* 32-bit IPv4 address */
/* network byte order */
char sin_zero[8]; /* unused */
};
//IPv6套接字地址结构
struct in6_addr {
uint8_t s6_addr; /* 128-bit IPv6 address */
/* network byte order */
}; #define SIN6_LEN /* required for compile-time tests */ struct sockaddr_in6 {
uint8_t sin6_len; /* len of structure */
sa_family_t sin6_family; /* AF_INET */
in_port_t sin6_port; /* 16-bit TCP or UDP port number */
/* network byte order */
uint32_t sin_flowinfo; /* flow information, undefined */
struct in6_addr sin6_addr; /* 32-bit IPv4 address */
/* network byte order */
uint32_t sin6_zero[8]; /* unused */
};
客户在调用函数connect前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。
调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或者出错时才返回。三次握手建立连接会在后面介绍。
3.bind函数
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrle);
当我们调用socket函数时,返回的socket描述字它存在于协议族中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind函数,否则系统会自动随机分配一个端口。
4.listen函数
#include<sys/socket.h>
int listen(int sockfd,int backlog);
其中参数backlog规定了内核应该为相应套接字排队的最大连接数。
listen函数仅由TCP服务器调用,当socket函数创建一个套接字时,它被假设为一个主动套接字。而listen函数的作用就是把一个未连接套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求,即将该套接字从CLOSED状态转换到LISTEN状态。
5.accept函数
#include<sys/socket.h>
int accept(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
accept函数由TCP服务器调用,用于从已完成连接队列对头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。在accept函数中,第一个参数是监听套接字描述符(由socket创建,然后用作bind和listen的第一个参数的描述符),它的返回值为已连接套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字。当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。
6.close函数
#include<sys/socket.h>
int close(int sockfd);
close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程。该套接字描述符不能在有调用进程时使用。
二.传输控制协议(TCP)
1.TCP协议
TCP是面向连接的通信协议,通过三次握手建立连接,所以只能用于端到端的通信。TCP提供一种可靠的数据流服务,当TCP向另一端发送数据时,它要求对端返回一个确认。如果没有收到确认,TCP就自动重传并等待,数次失败后TCP才放弃。注意TCP并不保证数据一定会被对方端点接收,准确来说,它提供的是数据的可靠传递或故障的可靠通知。
2.TCP三次握手建立连接
- 建立一个TCP连接会发生下述情形:
- 服务器必须准备好接收外来的连接,即被动打开,通常通过调用socket、bind和listen这3个函数来完成。
- 客户通过调用connect发起主动打开。这导致客户TCP发送一个SYN(同步)分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。
- 服务器必须确认(ACK)客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器在单个分节中发送SYN和对客户SYN的ACK(确认)。
- 客户必须确认服务器的SYN。
- 这种交换至少需要3个分组,因此称为TCP的三次握手,如图2所示。

图2 TCP三次握手建立连接
图2中,客户的初始序列号为J,服务器的初始序列号为K。ACK中的确认号是发送这个ACK的一端所期待的下一个序列号。因为SYN占据一个字节的序列号空间,所以每一个SYN的ACK中的确认号就是该SYN的初始序列号加1。类似的,每一个FIN(表示结束)的ACK中的确认号为该FIN的序列号加1。
3.TCP四次握手释放连接
- TCP建立一个连接需要3个分节,终止一个连接需4个分节。
- 某个应用进程首先调用close,即该端执行主动关闭,该端的TCP发送一个FIN分节,表示数据发送完毕。
- 接收到这个FIN的对端执行被动关闭。这个FIN由TCP确认,它的接收也作为一个文件结束符(EOF)传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用程序在相应连接上再无额外数据接收。
- 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字,这导致它的TCP也发送一个FIN。
- 接收这个最终的FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。

图3 TCP四次握手释放连接
类似SYN,一个FIN也占据一个字节的序列号空间,因此每个FIN的ACK确认号就是这个FIN的序列号加1。
4.TCP状态转换图
TCP连接建立和种植的操作可用状态状态转换图表示,如图4所示。

图4 TCP状态转换图
图中给出了11种TCP状态的名称,这些状态可使用netstat监视。
三.一个简单的客户/服务器程序
写了一个简单的TCP客户/服务器聊天程序作为全文总结:http://www.cnblogs.com/Rosanna/p/3494280.html
服务器代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
#include<stdio.h>#include<stdlib.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<arpa/inet.h>#include<netinet/in.h>#include<sys/socket.h>#include<sys/wait.h>#include <unistd.h>#define MAXSIZE 1024#define PORT 8080#define BACKLOG 10int main(int argc,char **argv){ int listenfd,connfd; struct sockaddr_in servaddr,cliaddr; socklen_t len; char message[MAXSIZE]; if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); exit(1); } else printf("socket create success!\n"); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_addr.s_addr=htonl(INADDR_ANY); servaddr.sin_port=htons(PORT); if((bind(listenfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr)))==-1) { perror("bind"); exit(1); } else printf("bind success!\n"); if(listen(listenfd,BACKLOG)==-1) { perror("listen"); exit(1); } else printf("sever is listening!\n"); for( ; ; ) { printf("*********开始聊天*********\n"); len=sizeof(struct sockaddr); if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))==-1) { perror("accept"); exit(1); } else printf("客户端:%s: %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port)); for( ; ; ) { bzero(message,MAXSIZE); printf("输入:"); fgets(message,MAXSIZE,stdin); if(!strncasecmp(message, "quit", 4)) { printf("终止聊天!\n"); break; } else len=send(connfd,message,strlen(message),0); if(len<0) { printf("发送失败"); break; } bzero(message,MAXSIZE); len=recv(connfd,message,MAXSIZE,0); if(len>0) printf("客户端:%s",message); else if(len<0) printf("接受消息失败!\n"); else printf("客户端不在线!\n"); } close(connfd); printf("是否退出服务器[Y/N]:"); bzero(message,MAXSIZE); fgets(message,MAXSIZE,stdin); if(!strncasecmp(message, "Y", 1)) { printf("服务器已退出!"); break; } } close(listenfd); return 0;} |
客户端代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/types.h>#include <unistd.h>#define MAXSIZE 1024#define PORT 8080int main(int argc, char **argv){ int sockfd; struct sockaddr_in servaddr; socklen_t len; char message[MAXSIZE]; if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); exit(1); } else printf("socket create success!\n"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); inet_aton(argv[1],&servaddr.sin_addr); if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))==-1) { perror("connect"); exit(1); } else printf("conncet success!\n"); for( ; ; ) { bzero(message,MAXSIZE); len=recv(sockfd,message,MAXSIZE,0); if(len>0) printf("服务器:%s",message); else { if(len<0) printf("接受消息失败!\n"); else printf("服务器已退出!\n"); break; } bzero(message,MAXSIZE); printf("输入:"); fgets(message,MAXSIZE,stdin); if(!strncasecmp(message, "quit", 4)) { printf("client 请求终止聊天!\n"); break; } else len = send(sockfd,message,strlen(message),0); if(len<0) { printf("消息发送失败!\n"); break; } } close(sockfd); return 0;} |
编译:
|
1
2
|
gcc -Wall server.c -o servergcc -Wall client.c -o client |
服务器运行结果:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
./server socket create success!bind success!sever is listening!*********开始聊天*********客户端:127.0.0.1: 60355输入:hello客户端不在线!输入:quit终止聊天!是否退出服务器[Y/N]:Y服务器已退出! |
客户端运行结果:
|
1
2
3
4
5
6
|
./client 127.0.0.1socket create success!conncet success!服务器:hello输入:quitclient 请求终止聊天! |
TCP套接字编程的更多相关文章
- 【UNIX网络编程(四)】TCP套接字编程具体分析
引言: 套接字编程事实上跟进程间通信有一定的相似性,可能也正由于此.stevens这位大神才会将套接字编程与进程间的通信都归为"网络编程",并分别写成了两本书<UNP1> ...
- UNP学习笔记1——基本TCP套接字编程
1 套接字地址结构 大多数套接字函数都需要一个指向套接字地址结构的指针作为参数.每个协议族都定义了自己的套接字结构.这些套接字的结构以sockaddr_开头,以每个协议族唯一的后缀名结尾. 1.1 I ...
- TCP套接字编程模型及实例
摘要: 本文讲述了TCP套接字编程模块,包括服务器端的创建套接字.绑定.监听.接受.读/写.终止连接,客户端的创建套接字.连接.读/写.终止连接.先给出实例,进而结合代码分析. PS:本文权当 ...
- 初探网络编程--TCP套接字编程演示
今天看了一下<计算机网络:自顶向下方法>,也就是计算机网络的教材的应用层一章,决定实现以下后面的Java C/S应用程序的例子,用来演示TCP和UDP套接字编程. 程序流程如下: 1.一台 ...
- 套接字编程相关函数(2:TCP套接字编程相关函数)
本文摘录自<UNIX网络编程 卷1>. 基本套接字函数 socket函数 为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型.其定义如下: #in ...
- 【UNIX网络编程(二)】基本TCP套接字编程函数
基于TCP客户/server程序的套接字函数图例如以下: 运行网络I/O.一个进程必须做的第一件事就是调用socket函数.指定期望的通信协议类型. #include <sys/socket.h ...
- <网络编程>基本TCP套接字编程
tcp提供了可靠传输,当tcp向另一端发送数据的时候,要求对端返回一个确认.如果没有接收到确认,tcp就重传数据并且等待更长时间,数次重传失败后,tcp才放弃. 建立一个tcp连接会发生如下事情: 服 ...
- unix网络编程——TCP套接字编程
TCP客户端和服务端所需的基本套接字.服务器先启动,之后的某个时刻客户端启动并试图连接到服务器.之后客户端向服务器发送请求,服务器处理请求,并给客户端一个响应.该过程一直持续下去,直到客户端关闭,给服 ...
- unix网络编程第四章----基于TCP套接字编程
为了执行网络I/O操作.进程必须做的第一件事情就是调用Socket函数.指定期待的通信协议 #include<sys/socket.h> int socket(int family,int ...
随机推荐
- oracle 11g 修改默认监听端口1521
OS: Oracle Linux Server release 5.7 DB: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - ...
- hdu 2425 Hiking Trip
题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=2425 Hiking Trip Description Hiking in the mountains ...
- JavaScript高级程序设计之数值数组排序
如果数组中全是Nunber类型,则可以按照数值大小排序 , , , , ]; // asc升序函数 function compareAsc(value1, value2) { if (value1 & ...
- Swift 中使用Nimble 库进行单元测试
Nimble 从字面上看是 敏捷,灵活 的意思.Nimble 是一个库,一个 断言库.这个库一般用于单元测试.Xcode 6 为我们集成了 XCTest 单元测试库.在正式介绍 Nimble 之前,我 ...
- 条款2:尽量以const、enum、inline替换#define
1> 以const替换#define • 比如用const double Ratio = 1.653替换#define RATIO 1.653 因为宏定义在预处理阶段就会被替换成其所指代的内容, ...
- MongoDB学习笔记-数据格式及数据类型
JSON JSON是一种简单的数据表示方式,它易于理解.易于解析.易于记忆.但从另一方面来说,因为只有null.布尔.数字.字符串.数组和对象这几种数据类型,所以JSON有一定局限性.例如,JSON没 ...
- 27.some company's Spi Flash chip replace altera epcsxxx
由于altera公司的epcsxxx芯片比较贵,所以一般用其它公司的spi flash芯片代替也可以.据AlteraFAE描述:“EPCS器件也是选用某家公司的SPIFlash,只是中间经过Alter ...
- Android实现传感器应用及位置服务
Android实现传感器应用及位置服务 开发工具:Andorid Studio 1.3 运行环境:Android 4.4 KitKat 代码实现 这里需用获取加速度传感器和地磁传感器,手机获取旋转的方 ...
- python的urllib2库详细使用说明
一直以来技术群里会有新入行的同学提问关于urllib和urllib2以及cookielib相关的问题.所以我打算在这里总结一下,避免大家反复回答同样的问题浪费资源. 这篇属于教程类的文字,如果你已经非 ...
- java中直接打印对象
java中直接打印对象,会调用对象.toString()方法.如果没有重写toString()方法会输出"类名+@+hasCode"值,hasCode是一个十六进制数 //没有重写 ...