在网络应用如火如荼的今天,熟悉TCP/IP网络编程,那是最好不过。如果你并不非常熟悉,不妨花几分钟读一读。 为了帮助快速理解,先上个图,典型的使用socket建立和使用TCP/UDP连接过程为(截图来源戳这里):

下面仅讲述TCP连接建立的过程。 (参考资料来自这里)

1. Initial State (初始阶段)

o TCP is connection based, ... establishing it is a complex multistage process
o initially all machines are the same
o no special 'server' machines
o the difference is all in the software
  • TCP是面向连接的协议,TCP连接的建立是一个复杂的多阶段的过程
  • 最开始所有机器状态都是一样的
  • 并不存在所谓的'server'机器
  • 所有的区别仅存在于软件之中

2. Passive Open (被动Open)

o server process does a 'passive' open on a port
o it waits for a client to connect
o at this stage there is no Internet network traffic
o tells the TCP layer which process to connect to
  • 服务器端进程在某个端口上执行一个"被动"open
  • 服务器端进程等待某个客户端来连接
  • 这一阶段并不存在Internet网络传输
  • 告知TCP层哪一个进程可以接受连接

3. Active Open (主动Open)

o client process usually on a different machine
o performs an 'active' open on the port
o port number at the client end is needed
usually automatic (e.g. 2397)
but can be chosen
o network message -> server machine
requests connection
  • 客户端进程通常情况下运行在另外一个机器上
  • 客户端进程在某个端口上执行一个"主动"Open
  • 在客户端,端口号也是需要的,通常是自动分配的(例如:2397),也可以主动选择一个端口号
  • 网络消息->服务器机器,需要一个连接

4. Rendezvous (集结,也就是连接建立)

o server side accepts and TCP connection established
o a bi-directional reliable byte-stream
o connection identified by both host/port numbers
e.g. <151.10017.25:2397/161.112.192.5:21>
o server port is not consumed
o can stay 'passive' open for more connections
o like telephone call desk: one number many lines
  • 服务器端接受连接,TCP连接得以建立
  • 连接是一个双向的可靠字节流(即全双工可靠字节流)
  • 识别一个连接,可以用host/port对,例如: 151.10017.25:2397/161.112.192.5:21
  • 服务器端口并没有被消耗殆尽
  • 服务器端可以一直处于"被动"open状态以接收更多的连接请求
  • 类似于电话呼叫台: 一个号码多条线路

5. More

o other clients can connect to the same port
o state for connections in the client/server only
o no information needed in the network
not like old style relay-based exchanges
o server can restrict access to specified host or port
o server can find out connected host/port
  • 其他客户端可以连接到同一个端口
  • 连接状态仅存在于client/server中
  • 与老式风格的基于中继的交换有所不同,tcp连接网络中不需要信息
  • 服务器可以对指定的主机或端口进行访问限制
  • 在服务器上可以找出已经连接的主机/端口

有关Passive open和Active open,区别如下:

passive - patient but lazy
active - industrious but impatient passive : waits for request for connection
: waits for ever
active : sends out request for connection
: times out o normally server does passive open
waiting for client
o but not always (e.g. ftp)
o active opens can rendezvous ... but may miss due to time-outs
o either can specify local port
but if not specified, allocated automatically

到此为止,我们已经弄明白了TCP连接建立的过程。下面给出一个简单的TCP client/server实现。

1. libfoo.h

 #ifndef _LIBFOO_H
#define _LIBFOO_H #ifdef __cplusplus
extern "C" {
#endif #define PORT 2345 extern int send_file(int sockfd, char *dstfile, char *srcfile);
extern int recv_file(int sockfd); #ifdef __cplusplus
}
#endif #endif /* _LIBFOO_H */

2. libfoo.c

 #include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include "libfoo.h" typedef struct file_header_s {
char *file[]; /* (dst) file absolute path */
size_t size; /* file size */
mode_t mode; /* file mode */
} file_header_t; /*
* send file via sockfd
*/
int
send_file(int sockfd, char *dstfile, char *srcfile)
{
int fd;
file_header_t fhr;
struct stat sb;
int rc;
int n, m;
size_t count;
char buf[] = { }; /* open src file */
if ((fd = open(srcfile, O_RDONLY)) < ) {
fprintf(stderr, "cannot open `%s' for reading: %s\n",
srcfile, strerror(errno));
return ;
} /* stat src file */
if ((rc = fstat(fd, &sb)) < ) {
fprintf(stderr, "cannot stat fd %d: %s\n",
fd, strerror(errno));
rc = ;
goto done;
} rc = ; /*
* create dst file header and send out
* o dst file path != src file path in case they are on the same host
* o dst file size == src file size
* o dst file mode == src file mode
*/
memset(&fhr, , sizeof (fhr));
strncpy((char *)fhr.file, dstfile, strlen(dstfile));
fhr.size = htonl(sb.st_size);
fhr.mode = htonl(sb.st_mode & ~S_IFMT); /* write file header to sockfd */
if ((n = write(sockfd, &fhr, sizeof (fhr))) == -) {
fprintf(stderr, "cannot write file header to sock %d: %s\n",
sockfd, strerror(errno));
rc = ;
goto done;
} /* read from fd, write to sockfd */
count = sb.st_size;
while (count > ) {
m = (count >= sizeof (buf)) ? sizeof(buf) : count; memset(buf, , sizeof (buf));
if ((n = read(fd, buf, m)) == -) {
fprintf(stderr, "fail to read %d bytes from fd %d\n",
m, fd);
rc = ;
goto done;
} if ((n = write(sockfd, buf, m)) == -) {
fprintf(stderr, "fail to write %d bytes to sock %d\n",
m, sockfd);
rc = ;
goto done;
} count -= m;
} done:
close(fd);
return rc;
} /*
* read from sockfd then save to file
*/
int
recv_file(int sockfd)
{
int fd;
file_header_t fhr;
char *file = NULL;
size_t filesize;
mode_t filemode;
size_t count;
int rc;
int n, m;
char buf[] = { }; /* 1. read file header */
if ((n = read(sockfd, &fhr, sizeof (fhr))) == -) {
fprintf(stderr, "fail to read file header from sock %d: %s\n",
sockfd, strerror(errno));
return ;
}
file = (char *)fhr.file;
filesize = ntohl(fhr.size);
filemode = ntohl(fhr.mode);
printf("> dst filename=%s, filesize=%u, filemode=%o\n",
file, filesize, (int)filemode); /* 2. open file to save */
(void) umask();
if ((fd = open(file, O_RDWR|O_CREAT|O_TRUNC, filemode)) < ) {
fprintf(stderr, "cannot create `%s' for writing: %s\n",
file, strerror(errno));
return ;
} rc = ; /* 3. read from sockfd and write to fd */
count = filesize;
while (count > ) {
m = count >= sizeof (buf) ? sizeof(buf) : count; memset(buf, , sizeof (buf));
if ((n = read(sockfd, buf, m)) == -) {
fprintf(stderr, "fail to read %d bytes from sock %d\n",
m, sockfd);
rc = ;
goto done;
} if ((n = write(fd, buf, m)) == -) {
fprintf(stderr, "fail to write %d bytes to file %d\n",
m, fd);
rc = ;
goto done;
} count -= m;
} done:
close(fd);
return rc;
}

3. server.c

 /**
* server.c - single connection tcp server
*/ #include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "libfoo.h" int
main(int argc, char *argv[])
{
int port = PORT;
int rc = ; if (argc != ) {
fprintf(stderr, "Usage: %s <ipv4>\n", argv[]);
return -;
}
char *ipv4 = argv[]; /* 1. socket */
int listen_fd = socket(PF_INET, SOCK_STREAM, );
if (listen_fd < ) {
fprintf(stderr, "E: socket(),%d,%s\n",
errno, strerror(errno));
return -;
} /* 2. bind */
struct sockaddr_in srv_addr;
memset(&srv_addr, , sizeof (struct sockaddr_in));
srv_addr.sin_family = AF_INET; rc = inet_pton(AF_INET, ipv4, &srv_addr.sin_addr);
if (rc != ) {
fprintf(stderr, "E: inet_pton(),%d,%s\n",
errno, strerror(errno));
return -;
}
srv_addr.sin_port = htons(port); rc = bind(listen_fd, (struct sockaddr *)&srv_addr, sizeof (srv_addr));
if (rc != ) {
fprintf(stderr, "E: bind(),%d,%s\n",
errno, strerror(errno));
return -;
} /* 3. lisen */
rc = listen(listen_fd, );
if (rc != ) {
fprintf(stderr, "E: listen(),%d,%s\n",
errno, strerror(errno));
return -;
} printf("Now tcp server is listening on %s:%d\n", ipv4, port); while () {
/* 4. accept */
struct sockaddr_in clnt_addr;
size_t clnt_addr_len = sizeof (struct sockaddr_in);
memset(&clnt_addr, , sizeof (struct sockaddr_in)); int conn_fd = accept(listen_fd,
(struct sockaddr *)&clnt_addr,
&clnt_addr_len);
if (conn_fd < ) {
fprintf(stderr, "E: accept(),%d,%s\n",
errno, strerror(errno));
return ;
} /* get IPv4/port of the server */
memset(&srv_addr, , sizeof (struct sockaddr_in));
size_t srv_addr_len = sizeof (struct sockaddr_in);
rc = getsockname(conn_fd,
(struct sockaddr *)&srv_addr,
&srv_addr_len);
if (rc != ) {
fprintf(stderr, "E: getsockname(),%d,%s\n",
errno, strerror(errno));
return ;
} char srvipv4[] = { };
const char *ptr1 = inet_ntop(AF_INET,
&srv_addr.sin_addr,
srvipv4,
sizeof (srvipv4)); /* get IPv4/port of the client */
rc = getpeername(conn_fd,
(struct sockaddr *)&clnt_addr,
&clnt_addr_len);
if (rc != ) {
fprintf(stderr, "E: getpeername(),%d,%s\n",
errno, strerror(errno));
return ;
} char clntipv4[] = { };
const char *ptr2 = inet_ntop(AF_INET,
&clnt_addr.sin_addr,
clntipv4,
sizeof (clntipv4)); printf("\nlocal %s port %d connected with %s port %d\n",
ptr1, (int)ntohs(srv_addr.sin_port),
ptr2, (int)ntohs(clnt_addr.sin_port)); /* 5. recv */
rc = recv_file(conn_fd);
if (rc != ) {
fprintf(stderr, "fail to recv file on fd %d\n",
conn_fd);
close(conn_fd);
continue;
} close(conn_fd);
} /* 6. shutdown */
close(listen_fd); return ;
}

4. client.c

 /**
* client.c - tcp client to send a file like scp
*/ #include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "libfoo.h" int
main(int argc, char *argv[])
{
int port = PORT;
int rc = ; if (argc != ) {
fprintf(stderr, "Usage %s <server> <srcfile> <dstfile>\n",
argv[]);
return -;
} /* 1. socket */
int sock_fd = socket(PF_INET, SOCK_STREAM, );
if (sock_fd < ) {
fprintf(stderr, "E: socket(),%d,%s\n",
errno, strerror(errno));
return -;
} char *ipv4 = argv[];
char *srcfile = argv[];
char *dstfile = argv[]; /* 2. connect */
struct sockaddr_in srv_addr;
memset(&srv_addr, , sizeof (struct sockaddr_in));
srv_addr.sin_family = AF_INET; rc = inet_pton(AF_INET, ipv4, &srv_addr.sin_addr);
if (rc != ) {
fprintf(stderr, "E: inet_pton(),%d,%s\n",
errno, strerror(errno));
return -;
}
srv_addr.sin_port = htons(port); rc = connect(sock_fd, (struct sockaddr *)&srv_addr, sizeof (srv_addr));
if (rc != ) {
fprintf(stderr, "E: bind(),%d,%s\n",
errno, strerror(errno));
return -;
} /* 3. send */
rc = send_file(sock_fd, dstfile, srcfile);
if (rc != ) {
fprintf(stderr, "fail to send srcfile %s to dstfile %s\n",
srcfile, dstfile);
close(sock_fd);
return ;
} printf("OKAY - send file %s to %s:%s successfully!\n",
srcfile, ipv4, dstfile); /* 4. shutdown */
close(sock_fd); return ;
}

5. Makefile

 CC      = gcc
CFLAGS = -g -Wall -std=gnu99 -m32 all: client server client: client.o libfoo.o
${CC} ${CFLAGS} -o $@ $^ client.o: client.c
${CC} ${CFLAGS} -c $< server: server.o libfoo.o
${CC} ${CFLAGS} -o $@ $^ server.o: server.c
${CC} ${CFLAGS} -c $< libfoo.o: libfoo.c libfoo.h
${CC} ${CFLAGS} -c $< clean:
rm -f *.o
clobber: clean
rm -f client server
cl: clobber

6. 编译并测试

$ make
gcc -g -Wall -std=gnu99 -m32 -c client.c
gcc -g -Wall -std=gnu99 -m32 -c libfoo.c
gcc -g -Wall -std=gnu99 -m32 -o client client.o libfoo.o
gcc -g -Wall -std=gnu99 -m32 -c server.c
gcc -g -Wall -std=gnu99 -m32 -o server server.o libfoo.o # --- Terminal : start server ------------------------------------------- $ ifconfig eth0 | grep 'inet addr'
inet addr:192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
$ ./server 192.168.1.100
Now tcp server is listening on 192.168.1.100: # --- Terminal : start client ------------------------------------------- $ rm -f /tmp/foo.c
$ ./client 192.168.1.100 /tmp/client.c /tmp/foo.c
OKAY - send file /tmp/client.c to 192.168.1.100:/tmp/foo.c successfully! $ diff /tmp/client.c /tmp/foo.c
$ echo $? # --- Back to Terminal ------------------------------------------------- $ ./server 192.168.1.100
Now tcp server is listening on 192.168.1.100: local 192.168.1.100 port connected with 192.168.1.100 port
> dst filename=/tmp/foo.c, filesize=, filemode=
^C

补充说明:

在一个TCP连接建立之后,我们可以通过socket描述符来获取本地的IP/port和连接对端的IP/port。

  • getsockname(): 用于获取与某个socket关联的本地协议地址
  • getpeername(): 用于获取与某个socket关联的外地协议地址
#include <sys/socket.h>
/* getsockname - get socket name */
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); /* getpeername - get name of connected peer socket */
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 在TCP客户端,如果没有调用bind函数,可以通过调用getsockname()函数获取由内核赋予该连接的本地IP地址和本地端口号;
  • 在TCP服务器端,一旦成功调用accept函数后,可以通过getpeername()函数获取当前连接的客户端的IP地址和端口号。

图说使用socket建立TCP连接的更多相关文章

  1. 不可不知的socket和TCP连接过程

    html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,h ...

  2. Linux 建立 TCP 连接的超时时间分析(解惑)

    Linux 系统默认的建立 TCP 连接的超时时间为 127 秒,对于许多客户端来说,这个时间都太长了, 特别是当这个客户端实际上是一个服务的时候,更希望能够尽早失败,以便能够选择其它的可用服务重新尝 ...

  3. 一个人也可以建立 TCP 连接呢

    今天(恰巧是今天)看到有人在 SegmentFault 上问「TCP server 为什么一个端口可以建立多个连接?」.提问者认为 client 端就不能使用相同的本地端口了.理论上来说,确定一条链路 ...

  4. 为什么建立TCP连接需要三次握手,为什么断开TCP连接需要四次握手,TIME_WAIT状态的意义

    为什么建立TCP连接需要三次握手? 原因:为了应对网络中存在的延迟的重复数组的问题 例子: 假设client发起连接的连接请求报文段在网络中没有丢失,而是在某个网络节点长时间滞留了,导致延迟到达ser ...

  5. 通过UDP建立TCP连接

    解释 通过UDP广播查询服务器的IP地址,然后再建立TCP点对点连接. 应用场景 在服务器IP未知时,并且已知服务器与客户端明确在一个局域网或者允许组播的子网下. 通过UDP发现服务器地址然后再进行T ...

  6. 最简单的理解 建立TCP连接 三次握手协议

     最简单的理解一:建立TCP连接:三次握手协议    客户端:我要对你讲话,你能听到吗:服务端:我能听到:而且我也要对你讲话,你能听到吗:客户端:我也能听到.…….互相开始通话…….. 二:关闭TCP ...

  7. 详解TCP三次握手(建立TCP连接过程)

    在讲述TCP三次握手,即建立TCP连接的过程之前,需要先介绍一下TCP协议的包结构. 这里只对涉及到三次握手过程的字段做解释 (1) 序号(Sequence number) 我们通过 TCP 协议将数 ...

  8. Python的网络编程[0] -> socket[2] -> 利用 socket 建立 TCP/UDP 通信

    Socket 目录 socket 的 TCP/IP 通信基本建立过程 socket 的 UDP 通信基本建立过程 socket 的 UDP 广播式通信基本建立过程 socket 的多线程通信建立过程 ...

  9. socket 建立网络连接,client && server

    client代码: package socket; import java.io.IOException; import java.net.Socket; /** * 客户端_聊天室 * * @aut ...

随机推荐

  1. LINQ to Entities 基于方法的查询语法

    1.投影: Select 与 SelectMany SelectMany操作符提供了将多个from子句组合起来的功能,相当于数据库中的多表连接查询,它将每个对象的结果合并成单个序列. 与 select ...

  2. ASP.NET2.0 Newtonsoft.Json 操作类分享

    JSON 是现在比较流行的数据交互格式,NET3.0+有自带类处理JSON,2.0的话需要借助Newtonsoft.Json来完成,不然自己写的话,很麻烦. 网上搜索下载 Newtonsoft.Jso ...

  3. C# GetHashCode、Equals函数和键值对集合的关系

    C# GetHashCode.Equals函数和键值对集合的关系 说明 HashCode:Hash码.特性:两个值,相同的的值生成的Hash肯定相同,Hash不同的值肯定不同. 下面一张图中,只有和“ ...

  4. Python学习之全局变量与global

    刚学习Python,遇到个问题:为什么有些定义在函数外的变量可以直接被函数使用,有些就不行呢? 如: count = 0 def change(): count += 1 change() # 报错 ...

  5. NETCore调用AD域验证

    一.添加引用 System.DirectoryServices System.DirectoryServices.AccountManagement 二.验证代码 声明域 string domainN ...

  6. mongodb 连接失败

    需要加一个配置文件,mongo.config bind_ip = 127.0.0.1 dbpath = D:\MongoDB\data\db logpath = D:\MongoDB\data\mon ...

  7. Django_Restframwork_序列号组件

     第一种序列化方式. 第二种方法通过Model_to_dict方法进行创建 第三种方式序列号组件Serializers: 第四种方法序列化 五.ModelSerializer组件. POST校验 PU ...

  8. python--基础数据类型 set集合

    一.set集合 set集合是python的一个基本数据类型,一般不是很常用.set中的元素是不重复的.无序的.里面的元素必须是可hash的(int, str, tuple, bool) 注意:   s ...

  9. 【OCP-12c】CUUG 071题库考试原题及答案解析(16)

    16.(7-5) choose the best answerThe PRODUCTS table has the following structure:Evaluate the following ...

  10. k_means算法的C++实现

    首先画出k_means算法的流程图: