管理员
论坛积分
分
威望 点
贡献值 个
金币 枚
|
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。# b2 ?1 S2 E3 B8 a( K
. M" X8 ~- n2 O1 L [
1 [9 k2 ?6 r; s( ? f# q# P+ F) Hsocket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
|/ _( j& g5 }% y
" N9 ^8 N% f$ }% u6 O. I1 h0 B0 |
) X7 Z. v- Y1 r; M7 r" NTCP协议 q! [! u. C; ]1 p9 \
TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。
9 n% _5 w! a5 f
6 _; N) D9 u/ m) w* t$ o; i: ^$ D& ~3 S+ @
关键词:三次握手,可靠,基于字节流。
6 @4 j' \0 E2 p% V0 c/ f" m2 {: T% v" Y, y& |
( i* h5 S& } k可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。& v8 j: w/ \9 [4 J0 y/ H
8 |% @) \( i' b8 y
TCP服务器端和客户端的运行流程
8 i. j! m% ?$ M4 O- g/ P, P9 ^( W4 L% N; E G* G
/ N- p% o+ E8 O7 p4 K$ e
如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?
7 f' H& t' Y; B: x' j1 y1 e2 L
0 @% C2 l% E A: N; e4 X9 G0 ~. l
1.创建socket
8 X5 n6 H; C+ a" U5 d- \6 f socket是一个结构体,被创建在内核中, R4 n9 U4 S6 o/ Y9 I( K/ o" S
sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议! a4 t$ e$ \0 _# y4 R. a
% R& g" R. W: p- t, ^+ B$ q: X4 r
2 t+ @4 q: m( n4 j; }
2.调用bind函数4 _6 S. M; g6 t7 X8 H0 O2 ?6 [
将socket和地址(包括ip、port)绑定。
I- \: `4 y) Z7 V( _6 ]4 e$ T 需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
1 P4 K' ^% {0 f. h/ a* \( m struct sockaddr_in myaddr; //地址结构体
9 l0 M/ f) O4 Z, R- w, r5 p bind函数
, e. q7 R( e8 Y7 q1 n/ o bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
8 i0 i; S! s1 ?" ]+ S, D# N# o% q
7 b+ A& H6 {/ F3.listen监听,将接收到的客户端连接放入队列" d f& @1 T: c/ {' A' X) d
listen(sockfd,8) //第二个参数是队列长度: ?% i* M1 F: p# ~) {0 C) J
5 K6 `7 C6 g( f. Z
: j8 A8 l0 H6 Z4.调用accept函数,从队列获取请求,返回socket描 述符 B7 _ _) m, K& W3 {/ H! e
如果无请求,将会阻塞,直到获得连接% ]4 ?+ Q* B9 B3 ` B+ \
int fd=accept(sockfd, NULL,NULL);//这边采用默认参数/ d) ^* K7 e5 C* t: q
) V; M. \0 f% i5 q2 O3 k3 T
! P1 I3 h3 M: ~5 ^$ V5.调用read/write进行双向通信
; Y9 ^$ g) N- B( i( ?1 S, V
0 L2 D2 q s: t! w2 m- F `( l+ u7 q& Z( A0 ] N# ]
6.关闭accept返回的socket
8 X; y! Z5 B9 S: h( P# f close(scokfd);
# A3 [' ?7 ^' s, ~4 N* Q
1 o; A+ W' c) t0 R4 C7 O9 P8 G
6 `- h4 e5 E Z; _
: B$ @$ X: A9 a: C: _1 A) u& J k% R; v4 a0 i( U2 o- n% R
下面放出完整代码
1 d* D8 n8 W4 v7 O+ s
" l4 J6 n# I9 B# @# Y/ W. M- /*服务器*/5 ?3 Y7 l( b" J7 b d& [: |; F/ O
- #include <stdio.h>5 o5 K5 U/ m* ~; K
- #include <string.h>
# H Q$ T# b1 C% a! w2 e - #include <stdlib.h>/ ` F& p6 ?- O% |- z
- #include <strings.h>) _) N' A! o( U- u
- #include <sys/types.h>( L7 w' ], u, I0 Y
- #include <sys/socket.h>( p0 h( \3 ]3 k* g
- #include <arpa/inet.h>
% K5 Q% F+ e/ c. Q- y7 l# \/ B - #include <netinet/in.h>
5 M B! u. {- U6 x! j6 M4 Z - int main()
: c. q0 H3 y* p# g7 [ d- x - {
. U" c, g% R* E* D" [ - int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
* ]) v' N! f/ H8 K - if (sockfd < 0)
1 v; s: d2 {. w- | - {
. }$ Z& q# `* `* }( I2 [ - perror("socket");. ~, y8 ]& Y. ~, p) Z$ l9 ^7 w; h
- return -1;* ]4 `- T& u+ k! Z
- } //创建失败的错误处理 @. I& G" V5 }3 K4 O% l
- printf("socket..............
0 m$ G: i, F- O% I: e. I - "); //成功则打印“socket。。。。”" i' c2 z( A4 l) Z: N5 D
-
- i1 G/ s5 b: g7 @9 p. p8 x - struct sockaddr_in myaddr; //创建“我的地址”结构体
( s; z2 @; o6 [, X - memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)7 K" |: g. |+ C6 c- E
- myaddr.sin_family = AF_INET; //选择IPV4地址类型
' P1 r5 m; \0 L- ^; P1 x9 B - myaddr.sin_port = htons(8888); //选择端口号9 `, m! p) K& d1 ~3 y5 l% M! u5 ~
- myaddr.sin_addr.s_addr = inet_addr("192.168.3.169"); //选择IP地址
1 L% V3 {( P8 w. A) i - - b) N, Z: |4 _: a: b0 I& O p9 T3 X
- if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
4 W6 e6 T, k/ {( Y) ]9 l5 c8 D2 d; d5 z - {3 U4 @) }" }) d- b
- perror("bind");
: S7 }8 i% k6 ]. U( t - return -1;9 Z4 }0 U5 t1 _! `' Z5 a
- }1 G& d a6 W O( a
- printf("bind........../ ?4 J9 M8 O# \5 f6 e
- ");
" q4 Y9 a. p7 P8 y -
+ q! W: |* K' a# M - if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听" H2 f6 J+ _% {& f
- {" i2 [6 q8 u' Y2 T, R8 Q: Q- Z3 m
- perror("listen");+ e' d5 C4 o( f4 W. P0 x% a0 ^# X
- return -1;4 Y0 t5 `% \- M3 d
- }+ t- O$ a% ~; ?# o& k: R2 G( M
- printf("listen............! E4 @6 p* g9 C6 \
- ");
4 s: V) D2 z* K8 D& `% T& Q- a -
/ [ f% X) g9 _8 V" U9 { - int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求5 i& \" n# f; ], |9 u& r X
- if (connfd < 0)
* z9 u& s$ X& S4 F m - {
8 c& ~+ z/ e) V6 J - perror("accept");
/ n/ \2 h6 V" s/ @: Y0 I. H - return -1;
: H8 ~6 g# k* P4 v4 S/ j - }
( p6 d L$ X( g2 R; X, C% V - printf("accept..............7 }5 |2 d* m& k) q/ u
- ");, q1 U* X; C9 t) ~$ f$ S
- char buf[100];//定义一个数组用来存储接收到的数据
$ u/ j* s8 \8 ~5 e8 g$ L; q - int ret;% `# D8 J$ l6 l8 q8 e7 `- z
- while (1)
- \2 t) ~* h. v6 }6 j R - {8 H/ J' Z: l/ X( M, v1 f
- memset(buf, 0, sizeof(buf));
[8 Y+ q+ M% r1 } f' s - ret = read(connfd, buf, sizeof(buf));/ K, v/ v1 L9 f' ]$ x+ }, a/ X
- if (0 > ret)
( w% L V3 i A+ Q7 d: Y6 e. d0 j - {$ d* C2 H( T0 ?
- perror("read");! }/ h: B6 M1 v+ v5 ?. q+ o" }
- break;" V$ ^3 `5 W) e2 {7 g( v5 V5 N
- }//执行while循环读取数据,当
% J* f0 N% M- I7 U+ N2 p$ W1 q2 Z8 v - else if (0 == ret)
, F- h. D8 S" z7 G - {% c9 V+ V) |' ?" p5 h
- printf("write close!
( H3 g1 l f4 E8 D7 x* e+ C - ");& b, x. C% W+ I, T5 l$ @! ]) A9 R
- break;
- c+ o7 P \' D5 m$ v8 m - }% q4 X5 j2 t- m; w( o( a! Y# @& J
- printf("recv: ");' A: j# i+ T' h4 q% j! _; v2 v
- fputs(buf, stdout);//打印接收到的数据
6 \# y- q7 h8 [' S - }+ f: j& J* j/ o1 K _7 O
- close(sockfd);//关闭套接字" K/ b: I6 Z4 V4 x2 g, O1 z& a( X
- close(connfd);//断开连接0 j0 c- r2 m0 q1 ^; d+ b( @; b) L
- return 0;
0 e; a0 f# ]" W U- [8 ~8 M6 @+ w - }
复制代码 " _, x) y; z* V' O- ^
/ Z9 A4 c, e. Y1 g3 @/ a
- /*客户端*/(具体功能和服务器一样,所以不再加注释)6 S1 A! K% h9 A* g
- #include <stdio.h>
9 d+ ?# N& x3 x - #include <string.h>3 `- Y1 J& H9 I* \& c
- #include <stdlib.h>
7 S. a% P' h" U& H: G6 T - #include <strings.h>
% h% ?; g2 e4 J: m) Z% m - #include <sys/types.h>' [% u, r. v. G/ Z% Z
- #include <sys/socket.h>
5 n7 S W- j! O - #include <netinet/in.h>
5 ~" L' l6 g( W; H5 ^- v1 ]/ y - #include <arpa/inet.h>( M$ \. Q! g) \3 W3 N
- int main()5 e r# m" N$ H7 H9 U
- {
( M8 h6 M0 k& i3 V+ G- a/ |% R, H+ r - int sockfd;
& w+ ~- g2 o4 P5 l - if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))
+ g8 E3 [$ Q* d6 C - {
. A% V0 c( ]1 [" p2 j6 D! V0 P! u - perror("socket");
# Z! E; T' q- `: h5 h5 k - return -1;
1 \" u! C+ j. P1 a) e - }
* m$ D, ^; W; [/ h+ c/ |/ h. U" F8 v - printf("socket...........3 C: R/ m. X' ~# a
- ");
" f3 ~& f- _; U7 @/ @; A3 e) L -
" {5 O9 u. E( g3 E7 G0 z - struct sockaddr_in srv_addr;
4 F: _+ R, P/ Z/ y# S5 t& A - memset(&srv_addr, 0, sizeof(srv_addr));$ g/ J) t* ` F% d
- srv_addr.sin_family = AF_INET;- p0 b6 o+ c7 }2 d3 l
- srv_addr.sin_port = htons(8888);0 z* Y4 V' y7 T8 c6 K1 ~
- srv_addr.sin_addr.s_addr = inet_addr("192.168.3.169");4 o W2 ~2 g i& J9 p1 e0 Q* e! Z
- if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
/ e! E* P# a+ F, ^# t, p& m - {+ U$ `! ?4 m$ J4 V1 f, z, b$ |2 c! g
- perror("connect");, T% J+ N. u2 U2 X1 m1 I1 I" h% `+ a
- return -1; //exit //pthread_exit
[& |9 X- P1 s. N3 F - }+ H, j4 _8 J) L t
- printf("connect..............; ^* L! d( ?3 t1 V" K4 x
- ");
' w; ]2 z, @7 @( @' Y. ` - char buf[100];
$ ?3 V3 V8 K# m - int ret;- n2 d8 U5 i1 E( q( m
- while (1)
; Y2 x- B% X! U8 y: ~; R8 _ - {
7 k' X% q) i7 }2 f2 F8 k4 K - printf("send: ");( ~+ p$ O" u2 k, J' d
- fgets(buf, sizeof(buf), stdin);1 i+ k$ T, B0 L( X7 I4 R6 q' w$ a E
- ret = write(sockfd, buf, sizeof(buf));
, A( H* Q4 @4 U - if (ret < 0)9 }7 y |! o" q+ c* @
- {
3 X4 r" x" \% Z7 @+ u: a - perror("write");2 Y+ w+ d0 Z5 m8 q2 c/ R
- break;
U1 B& G9 U1 k1 k5 C+ v# T - }
% g; ^7 z* ~) T+ l, V$ M/ @: Y8 n - if (strncmp(buf, "quit", 4) == 0)
3 E% T/ u- Q1 c2 y& l2 _2 b2 ] - break;5 `' I) |/ C2 M+ |
- }; X6 W3 H2 |2 u* L7 L6 L
- close(sockfd);$ _; `$ \( s# Z% {. |
- return 0;5 i9 j; p5 o# F3 @( {# Y
- }
复制代码
2 n6 n* C. N$ j0 w( ~
. K& R2 m$ e/ x" [) [: I& D/ E4 P |
|