在网络应用如火如荼的今天,熟悉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. Jquery 自定义插件写法(示例)

    (function ($) { $.SmsHelper = $.SmsHelper || {}; $.extend($.SmsHelper, { //插件具体实现代码 yzmnum: 60, Ajax ...

  2. solr介绍一:Analyzer(分析器)、Tokenizer(分词器)

    首先,不知道大家在前面的例子中没有试着搜索文本串,就是在第二节,我们添加了很多文档.如果字段值是一个文本.你如果只搜索这个字段的某个单词,是不是发现搜不到? 这就是因为我们没有配置Analyzer,因 ...

  3. mysql5.7 创建新表时提示时间戳非法

    # 背景 mysql版本5.7.8,需要创建新表,研发提供的sql文件,执行后报错如下: ERROR (): Invalid default value for 'deleted_at' 就猜测到时因 ...

  4. 启动hive命令时指定参数或自定义参数

    启动hive命令时指定参数或自定义参数 在hive启动命令中指定一个参数 hive --hiveconf hive.job.submit.username=fuxin.zhao -e "se ...

  5. java环境和Tomcat环境

    这些变量名是一样的,变量的值需要自己根据自己的安装位置来确定 JAVA_HOME C:\Program Files\Java\jdk1.8.0_151 CATALINA_HOME(这个可能不需要) D ...

  6. 窗口间传送数据wsprintf,WM_SETTEXT,SendMessage的理解

    对wsprintf  API函数的理解: int wsprintf ( LPTSTR lpOut, // pointer to buffer for output  LPCTSTR lpFmt, // ...

  7. Verify the Developer App certificate for your account is trusted on your device.

    1.报错内容 Could not launch "CH5203" Verify the Developer App certificate for your account is ...

  8. 【12c OCP】最新CUUG OCP-071考试题库(52题)

    52.(12-11) choose the best answer: Examine the structure and data in the PRICE_LIST table: You plan ...

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

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

  10. Nginx + uWSGI 配置django---终极版

    好开森,配置了差不多一天的项目,终于成功了,写一篇博客庆祝一下 我们先来了解下nginx与uwsgi的概念,再去配置 磨刀不误砍柴工. nginx 是一个开源的高性能的 HTTP 服务器和反向代理:1 ...