您尚未登录,请登录后浏览更多内容! 登录 | 立即注册

QQ登录

只需一步,快速开始

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 10906|回复: 0

[C] 自己动手用c语言写一个基于服务器和客户端(TCP)

[复制链接]
发表于 2020-5-9 02:09:24 | 显示全部楼层 |阅读模式
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。
) b( i% N+ U2 @( ~! ]% f7 ]' e9 ]$ G3 U

7 S1 ~5 y3 e- w. gsocket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
8 M# q  O8 B) G8 j; `( h  i, }
3 }' B$ g. c* n& }* B. Y- U' a

: k, O$ `# \) \7 DTCP协议
1 T, b: a3 b$ n" n/ S& cTCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。
$ i! Y5 I7 G4 a9 N1 h, T- v* y6 Q, \2 t. z

" n" _% u/ T% Y9 e8 d. @3 a关键词:三次握手,可靠,基于字节流。
- X: x9 }0 y1 o/ M: e# F9 E: D" Z' S
. W& d& E$ w8 F; X$ m! h" Z
; f/ B4 p0 B7 S4 P- k
可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。8 ^3 Y, @; p! ?; o) _  |3 `
微信截图_20200509015654.png 8 q" u* u+ e& ]& q/ [5 R, m0 y
TCP服务器端和客户端的运行流程
+ |. `; i  b$ e5 N
5 o% p$ R1 `5 {( K$ Q2 ]& ?: ]* n, [

" R/ o! \+ W* `: M2 p如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?: c4 {5 q( y# I1 A3 H( K

+ q  K* {' V% Q4 L' R

- J; a7 P1 P$ D+ L1.创建socket& N, W/ D8 Z8 e: e
socket是一个结构体,被创建在内核中
. Y, l: c/ S3 L+ z8 V sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议( v. p5 n9 V2 k6 w1 g; ^" Q
: N8 v# n$ T0 ~& F$ Y- o3 I( N# Q( W! A

/ X2 T8 }% `1 L8 ?5 G2 O2.调用bind函数8 t: E; Q& A- _, A8 L. u; D  O
将socket和地址(包括ip、port)绑定。0 K# I2 p. n9 u$ _' V
需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序- h& Z& j, O! G+ ~$ x
struct sockaddr_in myaddr; //地址结构体
$ N/ m" J+ @9 ^8 y1 E$ l/ M bind函数
; R/ x* u1 _9 W, Z bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))$ b- o8 e( ^+ e) R8 Z
+ _5 P% |0 q1 }3 |7 ?) Q$ |& v

# A* o) ^, R7 `! t8 g; @3 Q( e3.listen监听,将接收到的客户端连接放入队列
) g# `" B2 F# U7 v( r" @1 L8 B& d+ n listen(sockfd,8) //第二个参数是队列长度8 y0 R8 V: D0 I1 J/ x

+ d7 ]2 Y! D6 W0 ]3 x0 x
3 u' S! Q0 ]; w1 a
4.调用accept函数,从队列获取请求,返回socket描 述符
% d" |  T* o( {, R  如果无请求,将会阻塞,直到获得连接2 E( I; n9 r# H8 S2 h
  int fd=accept(sockfd, NULL,NULL);//这边采用默认参数! J5 P) Y4 e7 b& d6 q2 P7 t4 a
/ M& [! h( Q: m# L

# G* m* r9 m/ N6 A6 z3 j5.调用read/write进行双向通信
: _% k" i  q$ L, ~8 O+ r
" d8 V  a  l; A2 e! ~% B, B( u) h

+ o& t/ e" Z+ x" [6.关闭accept返回的socket. U/ p3 ]% Z, F  l* w" K
  close(scokfd);- P, R* N* w: V8 j9 ?1 W) h8 I
& U6 X# g, G2 ], g

+ Y! S  U8 r, p( r0 ~5 ^4 m8 S
, h2 Y1 e% D- K' y  g: ~$ ^& L1 n+ ?
* D- y5 K6 Z5 h0 `) w7 m
下面放出完整代码
3 J9 j; K; U4 \6 V5 x, f4 _8 w8 ~7 k  o
  1. /*服务器*/
    : P% a% r9 F+ L1 Y& ~: F
  2. #include <stdio.h>+ V5 q9 }1 U0 `, c* I" B2 A  b2 U
  3. #include <string.h>7 k) B1 v1 f# _- [" c, @8 F
  4. #include <stdlib.h>) R7 d1 }/ x4 W3 l* ^, i
  5. #include <strings.h>7 ~% K5 D, K0 I
  6. #include <sys/types.h>
    ; `; S& c# d" i4 y$ I9 A1 k- z
  7. #include <sys/socket.h>
    8 O/ n  H+ r% G: i' L9 d
  8. #include <arpa/inet.h>2 Z3 b- Z5 V# E. w
  9. #include <netinet/in.h>
    ' P+ [- E1 m- s/ \, J
  10. int main()5 r1 L- b7 p8 r% s5 o. d
  11. {
    + h* Z$ }- I7 o: y7 R
  12.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字5 m  A8 ]+ g! S2 C, I
  13.         if (sockfd < 0)
    " E2 H2 i+ O7 C: M6 }% x- t
  14.         {2 T8 l' ~) f, y6 Q, S, A
  15.                 perror("socket");% t7 S; N. a4 M% c' K
  16.                 return -1;
    ; U; j: {/ Q3 Z7 i6 y/ m
  17.         } //创建失败的错误处理$ y7 Q- k$ l' }/ a  w
  18.          printf("socket..............
    " U8 u. U7 Y% T* O/ d
  19. "); //成功则打印“socket。。。。”' A7 q. B, U* s: z. [9 v# `5 o% n
  20.          
    * v9 U$ ~) N/ \7 q
  21.          struct sockaddr_in myaddr; //创建“我的地址”结构体5 l' \. A7 N5 d6 }( k# ]: S
  22.          memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)
    / g7 k! g6 V" V" P( g9 M% K2 Y
  23.          myaddr.sin_family                 = AF_INET; //选择IPV4地址类型. t1 ~# b: g3 e6 D* u3 \
  24.          myaddr.sin_port                 = htons(8888); //选择端口号% V1 I  }  D! t* n; u' f% @
  25.          myaddr.sin_addr.s_addr         = inet_addr("192.168.3.169"); //选择IP地址" g5 l& \' {2 w' h" s

  26. & a: I4 c8 b8 k. Y
  27. if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
    1 J" O- s' V( J7 g& U  P. N. a
  28.          {; J# B& F; s- k7 w, w, \
  29.                  perror("bind");8 x6 B! ]' A) K$ R
  30.                  return -1;
    " l+ [: Z; }9 e5 V. q1 [$ {
  31.          }
    0 h/ y) w$ j; T
  32.          printf("bind..........6 f0 a* }. Z, C' a2 i& W; V; D
  33. ");% t2 c6 X( \8 E% N7 `8 f3 {
  34. ) m% W1 H( U/ A) _  t5 u* g0 a: G: J
  35.          if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听
    / ]9 q' S6 b# z/ S
  36.          {
    3 j; {0 }$ x4 J* R( N# [7 X
  37.                  perror("listen");0 W3 \% ^& S! Z% V
  38.                  return -1;
    * O$ J4 W( {; w9 K
  39.          }8 k: ^1 K' ~! r: z% o7 U8 @7 I
  40.          printf("listen............
    ( E; i' A' x5 o; N: p
  41. ");
    ! j' n3 `7 E6 i
  42.          3 l$ F9 f' `9 k% D/ F" n
  43.         int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求7 I; |) @" `& D' e; c4 K- Z+ x& y# J
  44.         if (connfd < 0)6 ^/ ]! x6 G: M2 Q* s
  45.         {- d. T' S. T* [$ T, i
  46.                 perror("accept");. ~( w9 |  {7 `" \( x8 ?
  47.                 return -1;2 }: _8 C$ Y! z3 b7 s* H
  48.         }/ |0 i5 y$ V% r# |' ~0 o
  49.         printf("accept..............
    / G4 W! q) [1 [7 V; u- c2 }& w
  50. ");
    ' U2 R; f7 G' a' C4 G/ E- X) y
  51.         char buf[100];//定义一个数组用来存储接收到的数据! v- q; O# n$ K9 n) d) c
  52.         int ret;
    ; L3 g* N8 Y2 _
  53.         while (1)3 ^4 I- ?+ i2 Y) W2 M% U
  54.         {
    5 ?+ R& q5 e& r" C) `2 q
  55.                 memset(buf, 0, sizeof(buf));
    8 I  V) t; l4 a+ e6 m1 N
  56.                 ret = read(connfd, buf, sizeof(buf));$ M3 V6 Q* H' ]. T/ q5 r
  57.                 if (0 > ret)
    ' Q' b( w, m2 f. k$ S3 p( h/ n
  58.                 {0 M0 c+ D9 l3 B: T- y4 J
  59.                         perror("read");9 ~2 Q% d! R* G8 h% _
  60.                         break;9 f7 [) v6 m7 b' {6 f: A" A8 t
  61.                 }//执行while循环读取数据,当
    1 M/ O4 e+ }8 O
  62.                 else if (0 == ret)
    - @( R6 i( u1 f1 L7 U
  63.                 {: ]7 o' z' t0 m+ l+ r1 j
  64.                         printf("write close!/ H' V' p; x8 f& w7 M
  65. ");
    4 M) ]! z* D! ~& s: M: c' Z
  66.                         break;
    ' Y' u  \/ h( ~3 ?+ m. d
  67.                 }0 w5 M  b) p9 o# h& _2 `$ W2 J+ n
  68.                 printf("recv: ");
    ! u# q8 ]# D+ r3 H# G# N
  69.                 fputs(buf, stdout);//打印接收到的数据; o" \% {8 L3 d+ R0 d7 q
  70.         }
    & [3 y3 t4 t  a/ q7 v; `
  71.         close(sockfd);//关闭套接字4 g3 z" C1 k  m2 Z# e
  72.         close(connfd);//断开连接
    2 i' a/ O" N6 B9 `1 t/ |9 h4 G
  73.         return 0;
    . B( W% X2 ?4 Q9 P/ A$ b
  74. }
复制代码
+ c% |/ w/ K, N" Z. R+ m- x

$ }( U) Q9 B/ l
  1. /*客户端*/(具体功能和服务器一样,所以不再加注释)) U1 p3 z. E* G8 i
  2. #include <stdio.h>0 Y/ i) p. e! U4 I; [; U
  3. #include <string.h>
    & m  N3 M8 {. @/ M
  4. #include <stdlib.h>* J& B) ?+ A2 H" |
  5. #include <strings.h>
    + z' q, W) p3 V
  6. #include <sys/types.h>1 u4 B4 b( D, V; d6 x
  7. #include <sys/socket.h>
      c# w/ m$ Z9 f8 P, s
  8. #include <netinet/in.h>; G4 ?2 P6 t1 q( K
  9. #include <arpa/inet.h>
    0 B6 j5 q9 ]# x$ U% T3 s
  10. int main()
    4 q" R4 Y* t6 U& [  q2 p& l
  11. {, C( B" c* Y$ c* H
  12. int sockfd;
    / n; t2 U! U! [) `3 s1 Q% a
  13.         if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))
    " j/ Y% X& K0 W; M$ s0 Q( k
  14.         {) ]' K2 w7 M4 q- n
  15.                 perror("socket");
    - _0 l7 D9 o+ a8 d( e0 s
  16.                 return -1;1 f) s* n0 P- D4 G0 B7 O0 ]+ |- o
  17.         }
    ; u3 Y, t8 _0 e0 {
  18.         printf("socket...........( o$ b4 y* Q. I, c" m
  19. ");
    & n4 T! H  N: u1 j" D/ |
  20.         
    2 t6 }6 A* w4 Q+ z* {
  21.         struct sockaddr_in srv_addr;
      p2 a9 B& y  x9 [4 T3 U
  22.         memset(&srv_addr, 0, sizeof(srv_addr));. v0 H: Y2 x. Y8 x0 t8 X4 q- ^
  23.         srv_addr.sin_family                 = AF_INET;: N; P; H. V) i* |) ^! V- C7 ~0 T
  24.         srv_addr.sin_port                         = htons(8888);
    ( d# F0 s; p& m0 b) a
  25.         srv_addr.sin_addr.s_addr         = inet_addr("192.168.3.169");. L7 @* @! J1 B  {; V' |" T
  26.         if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
    , Z1 i% Q4 `1 V
  27.         {
    % X) K, s4 O8 |; i) \" l" b$ L) ^
  28.                 perror("connect");8 T0 f, b' C" t+ `$ V7 @
  29.                 return -1; //exit //pthread_exit, z5 _: g! X) Q
  30.         }( e. j7 P1 J( Q8 @1 J! Q! V7 z
  31.         printf("connect..............
    2 v6 D" N& J, u, c0 G2 @) G2 L1 L
  32. ");
    0 h# \- B% w# L3 J0 K) I" S- z
  33.         char buf[100];# X" E6 z( ?& Q: [6 M  y3 i6 m
  34.         int ret;; ~3 }' B: w  |0 r+ x! D
  35.         while (1)
    8 Z% G. v- ?) ~6 ^
  36.         {! _0 M& j7 @! }5 V7 \1 n/ N2 Y
  37.                 printf("send: ");, B% q( j: `& ^8 v# e
  38.                 fgets(buf, sizeof(buf), stdin);8 X6 l9 M% G" _7 [( X
  39.                 ret = write(sockfd, buf, sizeof(buf));4 ~% v6 i) L' q- N
  40.                 if (ret < 0)
    ' r8 t. P; U, @, Q1 c' u
  41.                 {
    ! o" p' d5 n4 [/ r2 ~- N7 @
  42.                         perror("write");
    5 [( t: x, o; Q( ^+ X+ v  L
  43.                         break;8 [% C& z6 [$ d) c7 V
  44.                 }  [* t9 X6 R: [; |8 ]
  45.                 if (strncmp(buf, "quit", 4) == 0)
    " [9 B8 q% m7 ^6 V, v
  46.                         break;/ X+ R3 F6 R' }! K9 L
  47.         }, N0 H2 D6 N1 M, u1 q
  48.         close(sockfd);! j: n& B  b3 y' g7 `+ `$ i, S+ L
  49.         return 0;3 L% S# d+ q  _
  50. }
复制代码

8 e4 i5 E. A. @3 j! k9 C' @4 _) C* @
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

GMT+8, 2024-3-29 09:51 , Processed in 0.162620 second(s), 25 queries .

Copyright © 2001-2024 Powered by cncml! X3.2. Theme By cncml!