管理员
   
论坛积分
分
威望 点
贡献值 个
金币 枚
|
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。
# u, A |& N4 _( @3 ~# S1 M% r/ \$ [" g c
1 S2 |) e( M) R; f$ V# c" O
socket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
- V) z# u* H1 D3 o
/ y3 U: G* P5 |( B4 r/ [: Q1 l7 J, i1 y6 a& H( x
TCP协议6 |6 l1 y3 a; D# _2 L
TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。8 p* f$ G* [1 L- X7 D" D
; P4 c0 `( ~. ]! Q% Y" e
2 C j" E$ Y% Q
关键词:三次握手,可靠,基于字节流。
; C. n$ @: }8 X/ x+ b
0 X( r$ T7 Y% `1 m% s
( _0 }' j l. f& }( z可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。
6 z" f$ ]5 d2 x* J! x
( P, r3 ?' Z; C+ [TCP服务器端和客户端的运行流程
- @% v; a* E# z4 I; }0 S6 s, W1 y% ?3 n5 N
, H4 _9 p, T+ X8 B% T+ y$ ~
如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?
1 a/ y- S" D% p5 K
4 q4 {& h6 u% X: g" Y, L- T5 D1 {: W( Q, A; O
1.创建socket3 `. B* f) E- R3 e9 }7 l, w& q
socket是一个结构体,被创建在内核中" M! E1 C4 w6 N4 f0 M) D& D' S
sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议
& i. S2 n( x" x# q* C" [0 G; ]' O8 y9 ~( w9 w6 v+ v
9 v5 g2 H7 h1 @# { {2.调用bind函数" Q" V+ {( C0 V2 i
将socket和地址(包括ip、port)绑定。7 W7 [3 J- Y$ H0 \
需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
, u9 P% P+ T, `" b1 P) m* k struct sockaddr_in myaddr; //地址结构体8 u k$ T _4 ?% c+ ?. I; v5 P
bind函数. E3 F+ i9 P7 P V0 L4 H
bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
. p) s' G2 ?2 M+ l/ N; o# [+ U# Z% [# J Q
9 g8 n/ v6 s7 |( G
3.listen监听,将接收到的客户端连接放入队列! F+ C5 Q, G# c$ H, R9 {# O6 ^
listen(sockfd,8) //第二个参数是队列长度
, n& L Y0 O, G/ F" @
0 \3 l" E6 A5 x3 u( \8 T& R" U7 G4 `" q/ o- b
4.调用accept函数,从队列获取请求,返回socket描 述符
0 y6 W/ n: _' I 如果无请求,将会阻塞,直到获得连接
0 P/ Y5 |+ E% y* U* I int fd=accept(sockfd, NULL,NULL);//这边采用默认参数0 Y2 q) V# o) X% e# {- T7 \
5 _) ^* l( A6 _& h% [/ u
" t% R1 E$ G4 ?3 y& y5.调用read/write进行双向通信
+ _, h6 }# N5 R) `6 B7 V) c- d! Y! J$ P- G% u& W; U
, m- [ z7 @- @4 l5 E) M1 _5 K
6.关闭accept返回的socket
7 Q; h& H% I9 a9 F: V7 W4 B close(scokfd);
( `4 a& T" {+ W' ?+ S. \# f" e' O. t5 J: ?. Z& v
9 q6 E: i3 V; I$ F( p( N
) o# ~8 q1 W0 X1 }( U5 D
# M! I) ?8 f$ n& s5 ^. w) H
下面放出完整代码
. y6 F' E% V5 z0 n% N/ b* z& U- V$ u. U
- /*服务器*/
3 w* I- h& m% Q( C( D - #include <stdio.h>
5 S. T+ Y4 }) s* d' {: e - #include <string.h>
8 Q8 R" x* U& q! l - #include <stdlib.h>4 X3 _* y3 g# `# G/ b8 V+ g/ i) m$ |
- #include <strings.h>8 Q( B( C. S5 T; B% h" y* Z# w
- #include <sys/types.h>5 R2 y$ w, R3 p7 m9 y+ X
- #include <sys/socket.h>
6 I8 W: c9 D, |1 U9 _. } - #include <arpa/inet.h>% t8 F/ x4 ~; A9 Z+ }" T! y
- #include <netinet/in.h>8 L8 s8 ]- M& O/ B3 W0 g# Z4 m
- int main()
7 a- J6 ?$ Z# T7 ~ e0 A( x - {
2 t$ M) H) J0 _7 K2 T0 X& m - int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
' b8 f6 Q8 c# J3 | - if (sockfd < 0)9 F0 R9 Z0 t9 H9 _2 ]) i
- {0 q& j7 o# g& E2 |. ?
- perror("socket");
1 t; f3 H8 Q$ O# H! K# p c) ~ - return -1;& m8 ]/ G' K$ E7 U
- } //创建失败的错误处理# Y- _$ M; h6 g% R$ |5 ]- G) o
- printf("socket..............
/ }* ]% t }$ S, ` h& v3 m - "); //成功则打印“socket。。。。”7 V+ G; e: F/ i: l8 `8 _8 ~ g9 K
-
r* h( Z v5 k- q - struct sockaddr_in myaddr; //创建“我的地址”结构体
, }6 X$ o! {+ k1 x$ q# D - memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)( e8 z, W$ I' K6 n
- myaddr.sin_family = AF_INET; //选择IPV4地址类型
2 i4 ?, ?. `; F( Z& ~3 s( [# T - myaddr.sin_port = htons(8888); //选择端口号+ U3 b- g1 q& U3 l+ H
- myaddr.sin_addr.s_addr = inet_addr("192.168.3.169"); //选择IP地址( r8 O6 u# @ v9 e9 A3 o: r
-
0 {: m) C$ _) @( [: l' I - if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字/ A' T, Q( a7 o! g4 e# \ M, Y
- {
6 K- t/ s# z; L7 X- c% E - perror("bind");
" f7 x) h% O, w7 T2 \1 u. G - return -1;
% R, q& O# v, _% F - }
- X6 N7 l* ]( e3 f - printf("bind..........3 X7 p% ~" f9 Z4 p, E4 h
- ");
5 H3 _; g3 @1 \ b! M2 G/ h6 N2 k3 _ - . z" ?4 ^% V4 r3 t/ g: t9 K
- if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听
, g ?7 S" t2 _6 r- y% ?. \ - {5 A8 c# \ ~; Q) _) N
- perror("listen");
* o: m! w$ f3 h: T9 b6 J3 d L! k - return -1;& X a1 q2 h% P, `
- }
7 s% G3 `1 o6 @- r1 M7 m( r - printf("listen............' |$ }: L. w+ m* o/ n! Z# v
- ");
$ i4 H) G+ T: X& k* K( A8 l& M - 0 `2 @2 s6 j' i/ N2 n [
- int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求) N. C/ Y/ f% ~0 ]0 U- a6 f
- if (connfd < 0)4 x; k3 F5 H) b- ?3 C) J
- {' T% A v! i: o5 `+ F) C5 h, I8 I' h
- perror("accept");
* J5 Q) O+ l- ]8 r' ~7 f; O - return -1;8 a$ s6 n: f8 S% E' j4 b, P
- }% F7 O" f9 r. f( V1 u [( R
- printf("accept..............
! H! {0 C& ]8 k, t5 Y2 d - ");
. w$ U2 C K2 c* P" G - char buf[100];//定义一个数组用来存储接收到的数据
" o$ e) s z8 x+ |0 p6 z - int ret;" z) H5 C! b6 e( ~8 A' c2 I
- while (1). p9 g4 x3 D9 P+ Q/ L
- {
% p# h8 w+ v, t8 k - memset(buf, 0, sizeof(buf));+ ~) x' v% s8 _8 u+ h# |6 z
- ret = read(connfd, buf, sizeof(buf));1 Z) L* S' G+ E1 t( L
- if (0 > ret)
, ?6 M5 x n, I( ?+ G - {
; U+ [9 o. P% A - perror("read");
4 a! j/ r9 s/ }9 s( C3 Q - break;" E% l: S8 n; P% p9 x
- }//执行while循环读取数据,当
2 j& }: j5 i z# Q - else if (0 == ret)
/ B- m: J+ ?1 h& y( s) E% j# D) _ - {
* u) `# W; ?+ d( H! `6 j r - printf("write close!( m1 }; z j: I
- ");
) F) A$ m: a& n: z - break;8 p0 C$ c: Y7 m2 d0 i7 f8 U
- }
# P3 P+ |9 l% B+ t1 f5 l/ _ - printf("recv: ");
2 R& L& C! N' b! n9 B - fputs(buf, stdout);//打印接收到的数据
% j- i' m0 Q$ U' f - }0 {9 u* G6 p6 W% ^
- close(sockfd);//关闭套接字
) ~$ V+ p; `0 w M! L% p# w! y - close(connfd);//断开连接
, ~. e) s4 D$ J2 ]. [% N! q - return 0;9 V, R0 u9 h! ~+ K# G D/ B
- }
复制代码
" O! v8 ?% o$ p% x
( f" E W$ a# v. V" u- /*客户端*/(具体功能和服务器一样,所以不再加注释)
: ]9 o# k, p6 U* A$ U2 N/ N/ w - #include <stdio.h>2 C7 f1 l" N0 @
- #include <string.h>
5 j; L$ x2 t! z! X: {- U - #include <stdlib.h>
+ m) Z% p- p; O! n% S - #include <strings.h>+ k6 |9 Y" \# j' r( k; h
- #include <sys/types.h>
0 b2 |: W8 R6 a E. ^ - #include <sys/socket.h>
4 a8 x: T* T! m* f: M$ E - #include <netinet/in.h>6 B; f. t% d) J3 `! S: G
- #include <arpa/inet.h>. Q9 D0 V8 i" V; D3 A/ j
- int main()
" V. I6 F% C' P! m# L$ @ - {
/ u% ]# n2 _/ u6 J/ K - int sockfd;/ n3 l5 \' p/ {) [
- if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))2 ?' Q" V/ C; Z1 f! Y( N/ K
- {
; u7 T) N) S/ y9 } p1 u - perror("socket");) |5 s- X; u+ W, g
- return -1;( j, `+ X/ T9 K# e& M; S& ]
- }
. d5 o( a7 | A: R - printf("socket...........
, u& C) u/ q6 ^- u7 G( {( j - ");& g5 }2 q' |8 V) G5 G2 N4 T, y% S8 B, W
- ! E9 f$ [. a) s8 l! r
- struct sockaddr_in srv_addr;0 E$ V4 @% h, |4 n: y. l
- memset(&srv_addr, 0, sizeof(srv_addr));
: j& ]4 V( \5 z! c p' Q8 K" C) Z4 {3 f - srv_addr.sin_family = AF_INET;/ s3 j: k& ~8 W3 i7 R
- srv_addr.sin_port = htons(8888);- |5 {0 o4 U# ?( S4 L7 h
- srv_addr.sin_addr.s_addr = inet_addr("192.168.3.169");
3 f( i e6 c8 e2 w - if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))0 R( x9 \( e: ^& u5 r$ O
- {1 Q& L* }/ j$ O7 y5 x5 Q$ v
- perror("connect");
8 M% @; Q+ v( s2 f% g) T - return -1; //exit //pthread_exit8 Y J5 D r' z. K) W* Z* m9 S
- }, n. E( x% S2 c. v( B
- printf("connect..............# k/ E# Y! N8 m# d9 w
- ");
2 m( z% v0 L8 ~6 Q - char buf[100];. Y' `* N1 w! s5 }/ \7 X
- int ret;
9 ]+ u9 R; l! l* G - while (1)' U9 e2 f0 Z- Y. H5 M' ?# }7 V
- {
) I" I2 I! l8 i$ }& R7 o1 B& _' e - printf("send: ");4 [9 Z/ g! O/ C/ V6 D& r5 }
- fgets(buf, sizeof(buf), stdin);
' h3 [5 p: H4 W' K" }& {9 i0 n' D - ret = write(sockfd, buf, sizeof(buf));
$ m g# @$ v8 b) Y - if (ret < 0)
, X. c$ L4 J m; ^" r, x# j7 Y - {8 P7 f0 N7 i# s7 e2 d* S7 u1 I( M0 ^: v
- perror("write");) G" X0 i* Y7 h: g" C5 X* }# O
- break;% N8 o8 p* _" y7 E
- }% h' o" P! G' P9 p$ p; X: B1 I
- if (strncmp(buf, "quit", 4) == 0)
5 A; {1 k" B% b4 _; ]! j - break;- D! e) p7 _; C' S: c
- }
1 i9 }1 E A/ k' k6 T; [7 b - close(sockfd);
+ k+ E8 N1 [' \1 L0 R1 ?4 }5 N- \ - return 0;
* H5 H7 ^) c& D& X5 V& O - }
复制代码 ' ^. \. v- A7 N1 }0 a2 t' [5 W
- O. Z$ p( ? H! |4 ]8 |! J
|
|