管理员
论坛积分
分
威望 点
贡献值 个
金币 枚
|
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。
. T4 M& J' H" N8 h" `" \6 v
9 J: \' d! [ h/ G: U5 ~
* B9 t$ Z1 I' m# q' d9 J1 lsocket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。8 ?2 l. `2 K& S" \( `
y% p8 F# D1 z X E: X, Z+ q8 Z3 {: u5 ^& Y0 P/ n! t
TCP协议, h: W+ K* n0 v) K1 {5 @
TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。2 V& r4 S- r( {0 c' Q- u: Z
) M1 Y" i+ I7 a! O$ S4 T! C/ I3 E9 `5 W' [
关键词:三次握手,可靠,基于字节流。
b/ s/ }* U4 @9 }) [- i7 a' }1 L" f' ~3 u
3 e# F' m0 q7 E0 d O% f
可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。' h+ V( M7 |( N% t4 W
2 j* M7 H7 E( j* d
TCP服务器端和客户端的运行流程
+ C1 p3 u" _$ Y4 t0 X9 K7 s8 L W& A$ u5 y) q
! ?2 _& c H- v, C
如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?
- I/ U* a+ ]8 u& R; ~, ^& O/ t& s8 @+ I- z- D/ K
! P ]" F5 \, t. E" I- f7 B1.创建socket
3 [, d: L9 U2 Q0 \. V. Z: J8 {7 e) E6 { socket是一个结构体,被创建在内核中
$ ?4 R: f+ w/ }9 D sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议. j, }3 z0 p) ?
+ J% g' }/ D z; M0 y# p3 [
. D5 Q2 X2 d3 d2.调用bind函数
- q& \# n1 p7 f! t 将socket和地址(包括ip、port)绑定。
( {' F2 w4 N6 r- d2 c1 A P" }; A 需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
: f2 ?0 h! ?9 b, @0 s T0 [+ x struct sockaddr_in myaddr; //地址结构体% r+ k2 S4 B+ d% |4 D
bind函数3 U( W% [5 e# f# x
bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
, e d7 U: {$ G& ?0 A3 k: ^6 J0 H) H3 X/ \1 R) p
7 m0 p! v! @0 H4 Q9 G5 g3.listen监听,将接收到的客户端连接放入队列; K; t% V' h$ S* l$ Y6 h& J
listen(sockfd,8) //第二个参数是队列长度
( Y3 g% ]$ F+ `. ~) n4 c& u/ d$ d+ ^; k3 K/ z. i' x3 P5 K7 {2 f
: m I3 b4 B$ }: b0 C: {4 D
4.调用accept函数,从队列获取请求,返回socket描 述符7 e, `( p6 w L o6 T, ^
如果无请求,将会阻塞,直到获得连接
4 [9 G4 ?& Y' k int fd=accept(sockfd, NULL,NULL);//这边采用默认参数
3 U* d% j& L% r B
+ K8 b6 J- f. s* n1 D7 Y6 d: Q$ Y1 v# b3 ~
5.调用read/write进行双向通信
. x3 D A4 y: w$ d( Q
. [8 Q( k+ m; c: S
8 k" C! c; m1 q, i$ u6.关闭accept返回的socket O$ g& u8 C& P, Z- U- a: j
close(scokfd);: S8 p, Y0 ]+ B. i3 `
1 {7 z% t- r; P5 B. Z9 u2 Y9 J, O; l4 L0 x3 X
9 W7 H5 G1 u% J: k8 d
5 }0 m8 N: c. N) n下面放出完整代码. r7 e; m/ c1 @2 I
) w8 p4 h; Q' T, c `( [
- /*服务器*/1 _0 |) f8 ?- t# }$ ?
- #include <stdio.h>
1 c4 _5 i6 y9 m - #include <string.h>+ i6 ~& Q0 M2 K; k4 s
- #include <stdlib.h>
2 m" d' }2 R9 C, c p7 S - #include <strings.h>7 e9 p; |- ?9 C" t4 o
- #include <sys/types.h># Q8 q+ E: H, z
- #include <sys/socket.h>
1 ?# X$ F8 l' i6 }" h( Y - #include <arpa/inet.h>. n" i g' i! s- L
- #include <netinet/in.h>
2 I3 M0 N% l" J1 P - int main()' `+ l% S. t8 S
- {
6 K+ P3 f$ `( G1 c - int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
6 C* A: c* [ i4 }+ m7 D. N - if (sockfd < 0)
, K4 {5 k3 z6 D+ s - {+ v- g& F I! O* S* A. z3 V
- perror("socket");
: w+ D, _) _- B; E& E - return -1;
7 t! j" C; J$ V+ L0 P' i - } //创建失败的错误处理
4 m$ P) I- c1 x4 V/ f - printf("socket..............
+ F; f: i: _" ~& ]9 {5 l/ n - "); //成功则打印“socket。。。。”
, s0 N' ?4 e. q1 Q/ ]3 w -
1 T! M- K6 o* S+ u+ O9 c( G# s7 k - struct sockaddr_in myaddr; //创建“我的地址”结构体" o; ~$ G* [3 W3 C3 F
- memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)3 U9 V9 W0 a0 k V0 \3 m
- myaddr.sin_family = AF_INET; //选择IPV4地址类型
" r- ` B) \3 k8 w+ I% c; g" Z - myaddr.sin_port = htons(8888); //选择端口号5 Y/ V% @. G' B& M/ q
- myaddr.sin_addr.s_addr = inet_addr("192.168.3.169"); //选择IP地址
4 w8 d/ r- K6 B8 t7 E$ c/ h -
; V: t. T( O5 q - if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
- c& ~5 O7 ?' } - {
" s3 n: {& g* @6 ?$ {% ?4 N - perror("bind");
! m) }( s5 a& N' ~ - return -1;
1 ~1 u" G2 ?/ f6 V - }& A3 F8 A; N: T9 Z6 o$ B
- printf("bind..........4 i9 A. w! W1 s' K3 [! {$ t# D- f- d
- ");3 `% S, l8 ^9 E3 T
- , X2 L2 `9 _. Y* s% g% S+ k* V" R
- if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听4 R8 U& s s4 f; ~3 l9 z
- {/ l& o8 s$ v4 `8 M/ E& A9 l
- perror("listen");
9 q6 }4 V1 L6 P5 }! ` - return -1;
' q6 r' W& G$ l& ?" M& Y) \: A - }
! `. n" K( _% k$ N - printf("listen............8 e1 S$ ]; a& k0 w* C0 M7 A; `4 w
- ");5 d6 C% X( j, w8 a; S9 Y: i) F0 Z
-
2 T. ?. {- R$ d+ b3 {; V) ]4 p - int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求6 B7 H# H. X" z7 p
- if (connfd < 0)4 y. ?2 d6 X- v+ h0 `
- {/ S2 t' D% _( [
- perror("accept");/ a0 f8 a0 g2 h) Y) ^1 E9 F- G
- return -1;
2 d/ Z3 x& i/ o# {0 z2 n0 C) V0 k3 N' G - }. y- O; c# q5 a2 F
- printf("accept..............4 J- P, }8 O6 b2 A% c
- ");1 W( ]) L7 R# v$ e% k. O0 ~
- char buf[100];//定义一个数组用来存储接收到的数据
3 e& [7 e8 P7 q6 f% | - int ret;
7 _% z* Z* O" d5 v" G }% r9 w- A1 ~1 x - while (1)
/ U; j# x# }8 v - {
6 p' i8 l9 n8 U* t - memset(buf, 0, sizeof(buf));; D& |/ { s6 R/ ] }! V
- ret = read(connfd, buf, sizeof(buf));; i7 Y! d. |5 N1 j2 Z; w
- if (0 > ret)
& H. u' b0 M' i5 n( f N5 Z0 k/ a - {
( N) f5 Q' b9 K, s9 _+ n+ [& o - perror("read");
" d# W! m, ?% L" J; c3 G8 Z5 t - break;; f. B2 t# G4 N4 \+ B) v1 u' r
- }//执行while循环读取数据,当# B7 |" x1 n9 ]
- else if (0 == ret)
7 P" f8 p% c3 t0 P( q - {
$ [3 m$ ?% t9 l& g( Q& C8 K: | - printf("write close!! b7 k3 q L. [% [# D! t# H& ^ \0 l
- ");
; Y5 Q# h5 b9 l7 J4 d5 u3 `* d - break;) Q$ |1 J O5 g/ V. k4 `
- }
$ G1 l7 W, v0 g/ X* i0 m9 v - printf("recv: "); U, {* n& L; _2 n* t- C3 p# R
- fputs(buf, stdout);//打印接收到的数据
! X% d5 n7 T! d8 q: ?" a# g - }6 Z l# V" u7 l% A4 N
- close(sockfd);//关闭套接字
/ W7 K. P2 c0 U! j$ v. R( P4 ~ - close(connfd);//断开连接2 {7 W( E9 ~0 `% H o% v, ^9 t7 o4 l$ `
- return 0;
% @3 G7 U) a9 K. w4 V - }
复制代码 % e0 c% }9 H2 k* @/ ]
# F' p; ]0 B! C' A! {4 {" @$ O
- /*客户端*/(具体功能和服务器一样,所以不再加注释)
' b% E* v$ v+ U4 T& K) W6 { - #include <stdio.h>4 E% d* T* x. @9 p, [
- #include <string.h>+ e/ l& B& |+ ]& [7 s! c0 z
- #include <stdlib.h>
7 G: [6 k0 _4 ?" s. K1 s# `5 J - #include <strings.h>
% K/ m+ U, g$ J" p% ^4 O0 j - #include <sys/types.h> a+ `# I6 p+ I. y
- #include <sys/socket.h>; R9 g6 \) W' R- f3 e$ b
- #include <netinet/in.h>$ s) f, F3 C3 u# d2 x
- #include <arpa/inet.h>& g8 S* [2 y9 Z/ b/ E' ^9 K2 @
- int main()
5 _) v9 }" f3 _# H' W$ g! R - {
. F+ D4 Y! K: [+ Y1 I+ X - int sockfd;2 F9 x S0 O/ |' \1 Q( {. W& q' m
- if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0))) M9 {9 |3 c) S7 A9 O) y: Q" i$ n
- {0 u3 I% V- _3 S1 s! P1 T6 j( n
- perror("socket");& q1 j# g& ] k3 l2 D# s! o, t
- return -1;6 E5 k# s6 M$ f& s6 B% z7 S
- }2 g, H4 x) s: P4 M, w u8 W
- printf("socket...........
. F. Y) c& b. m' W, C - ");
" T2 l$ o5 u5 h8 V `4 A0 F - 1 b( b/ N8 Q$ ^( s" p
- struct sockaddr_in srv_addr;
) o5 J4 `2 w3 q! y& s+ P3 ^; u - memset(&srv_addr, 0, sizeof(srv_addr));
9 L2 n" t2 O9 T* N8 u8 W - srv_addr.sin_family = AF_INET;/ {* v! n) \7 v1 |* c1 H9 r
- srv_addr.sin_port = htons(8888);
0 u5 ~, ^9 m/ j2 R5 S& Z7 O4 A - srv_addr.sin_addr.s_addr = inet_addr("192.168.3.169");
* H% y$ m& P2 Y7 a/ Q! U1 O - if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
$ b8 K' k% v# S; p& b0 [. ^' Y6 o" b - {* J" b; W/ {/ D$ U; c0 D
- perror("connect");6 _& C3 f2 a; E8 \! |% L
- return -1; //exit //pthread_exit
: g! d' {- E% R7 n4 `+ ^; A - }
$ [- q( y- n5 Q) u - printf("connect..............
: c- c2 u7 I2 ? - ");4 Z }$ g6 ?8 R# g9 e2 V
- char buf[100];
$ w8 t! T5 _, e1 G6 z - int ret;
9 G, C# l8 {' t' z6 Y' [6 q0 q - while (1)
) u. S2 Y! S" J4 M. K5 V7 ~ - {
8 L. k4 N* V0 C/ U6 V, B" S - printf("send: ");" @2 k O. G9 q" ~; C
- fgets(buf, sizeof(buf), stdin);* V7 B! l" ^; g& r& p/ \0 M6 C
- ret = write(sockfd, buf, sizeof(buf));! b+ C+ \* b) C3 l7 P
- if (ret < 0)1 Y' o% Y X. E. \& _
- {6 ~9 C. S/ q E1 d1 [
- perror("write");( A* l+ z) t! [6 F; Y
- break;
5 [# D) H( y0 P) O! F - }0 F" h7 b3 S3 ?% a9 S6 D
- if (strncmp(buf, "quit", 4) == 0)
! s. s6 b: J$ `- A - break;( v; ~" u$ w0 u* R j
- }
5 }* Z7 i8 y. Y7 c - close(sockfd);
0 q1 z" D5 |& H5 ^: P - return 0;
1 l( H1 d3 z5 g4 A, }8 x - }
复制代码
- H: d# ?$ `8 r6 E# z* P ^
# c3 A9 q! A/ b7 Y! J! O8 ^, ] |
|