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

QQ登录

只需一步,快速开始

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9707|回复: 0

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

[复制链接]
发表于 2020-5-9 02:09:24 | 显示全部楼层 |阅读模式
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。( P1 e+ i  f& J9 J0 L9 P5 u- p
% b! w& m' p3 p" X& f. _# ]

- J" W* X- `# A4 N5 E* I3 ?socket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。7 X$ d, @, z& b: W. R% r

& _0 s1 I* L- G
+ W' g% t- E' r: V3 K
TCP协议
; L  p. N, i) J. @. J5 k: pTCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。
6 {3 H: S0 }( ]; S2 }) o" p' \" z  ]6 O3 K  g. B& B* O2 w2 G8 e/ ?

* u: j$ l7 m  q- ]1 D  r+ M' B关键词:三次握手,可靠,基于字节流。
4 @+ q& {/ l& ?- i6 U3 o0 a) }
5 T1 E& y0 O/ @! D6 J. Z
3 _" a/ O+ r& C- x$ n6 r7 o
可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。4 q9 J8 R( y9 p) }- l4 p! E' B
微信截图_20200509015654.png
3 @: q4 R7 N! G6 K& m+ x# z2 UTCP服务器端和客户端的运行流程% [/ R4 Z& T0 R6 s" Z
. x' }( D* d- L" a% ]- [0 q

# j' I0 M1 o1 I' K+ t+ w# o如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?
( E2 W. a( b( }5 I# Q- X) B7 z! B+ J: i

+ Q7 f; }) d: b1.创建socket. Y1 r7 A; ]8 ^4 {1 T8 F. j
socket是一个结构体,被创建在内核中
% c  K2 C+ Q- K$ w8 C sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议
7 R2 R& O8 J# l
! y" z2 }* I+ ~4 \1 @
. @# d& ?2 U1 i% j0 I6 T
2.调用bind函数
+ V& S, z9 j1 z" U9 Y# o 将socket和地址(包括ip、port)绑定。
2 S6 Q3 x3 Z8 B, r# S7 R% u  [ 需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序0 m4 d- w1 m; e. c! h) T1 L; K: C
struct sockaddr_in myaddr; //地址结构体
+ j# a. M. i' t, W$ D4 q) G! e bind函数7 S6 j% o5 R" C
bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))
0 N; E5 M7 W3 \% w8 e2 b( `4 _6 f& u5 |
+ ], }9 Y0 F( |
3.listen监听,将接收到的客户端连接放入队列; a, B8 l( M+ c, W$ [: L
listen(sockfd,8) //第二个参数是队列长度
4 R+ v% p8 N0 A$ ?- {2 b0 i7 [9 a2 d, q, l

4 M: A1 x* }7 i# e+ o  s4 A4.调用accept函数,从队列获取请求,返回socket描 述符
# S- L# S1 _$ s2 T1 l  如果无请求,将会阻塞,直到获得连接8 _- z8 {! W& R8 j7 Y
  int fd=accept(sockfd, NULL,NULL);//这边采用默认参数8 N* I" i' `/ n3 Q# ]  b
( l3 c7 j7 i+ Z1 u( Y

7 N- @; G- ]3 n% D5.调用read/write进行双向通信
; ~% B, s9 E8 v" }# c3 O5 N& v7 P5 O5 W3 f' P  S# v+ R/ p
3 ^- B9 d# R2 I! z- f
6.关闭accept返回的socket" o( j, G+ H" U2 J& a
  close(scokfd);! x$ c; I9 P! L: c- j' y
8 w3 N+ ^( F0 F& ^/ d  `1 F* |

; E3 u3 p5 {$ [9 j0 _5 x" s9 N6 |+ |3 n+ Y7 H5 f, i8 C" @
5 R' n  Z1 b$ ~
下面放出完整代码. [! J4 a/ }' I" K8 P' V/ y% i
9 f$ m& l5 |8 m# }6 D
  1. /*服务器*/0 N! u) @- t- _  l
  2. #include <stdio.h>6 I, }; U' @) k* O# _) D
  3. #include <string.h>
    3 f9 y; a! F6 \/ c% s8 l; V& A
  4. #include <stdlib.h>  {; p) Q5 u- Q$ P5 T4 @7 g' K
  5. #include <strings.h>
    ' m8 h- ~$ i# B1 G1 U) T& R' R4 }5 p3 p
  6. #include <sys/types.h>
    7 E/ b1 N2 l6 _8 E
  7. #include <sys/socket.h>
    2 u7 k1 O3 ?/ _6 Y2 C# _8 p/ @3 T3 W$ g
  8. #include <arpa/inet.h>
    / C& N6 \+ L$ q# ^7 R# u
  9. #include <netinet/in.h>- C8 t" i, N7 G; ^' w
  10. int main()* w. p) C+ r+ W8 Q3 N
  11. {
    2 R. B5 h& ~% Y6 e! B' @! o. `; L* o
  12.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字, e2 n& f# H3 Q1 \, Q$ N" h( }
  13.         if (sockfd < 0)
    , q( a1 Q2 Z' f% H+ X! g0 S
  14.         {) L. R/ ?7 Q/ A$ {  ]  |
  15.                 perror("socket");
    4 w* }# e. I7 j1 d' h
  16.                 return -1;( H) V7 e0 a9 J1 a" ~! t$ E$ n
  17.         } //创建失败的错误处理
    7 C9 c  F: l) H% }4 W
  18.          printf("socket..............
    ; F4 O* u9 ~* }
  19. "); //成功则打印“socket。。。。”
    / ]2 W# k" I% h" w. ]
  20.          - h& `& }( Q' L3 B
  21.          struct sockaddr_in myaddr; //创建“我的地址”结构体
    6 |: l5 J  o$ H" |0 @: M6 b
  22.          memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)4 K% M5 x' B( G( @: U4 v
  23.          myaddr.sin_family                 = AF_INET; //选择IPV4地址类型! H# J7 ^. L4 C0 Q/ C- j0 i; v
  24.          myaddr.sin_port                 = htons(8888); //选择端口号
    & i+ x8 U0 y) O3 p9 R: g+ ?/ O7 {
  25.          myaddr.sin_addr.s_addr         = inet_addr("192.168.3.169"); //选择IP地址
    " q$ K/ `% o! m
  26. ! y9 Z3 [' Z+ v, J8 I
  27. if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字
    - j+ \2 {. j" K- w- F2 m/ w! z3 |$ K
  28.          {
    & @+ Z  ^% S( y" P& Y4 X0 C8 x! v
  29.                  perror("bind");; |& d- d6 y* z6 p
  30.                  return -1;
    , Y8 ^3 h" \+ d( B& d5 O, {3 K9 K
  31.          }  P3 r' H  R# R1 l+ |' ?7 M5 N
  32.          printf("bind..........
    + [" Z" b* I; g4 i. d  j' p
  33. ");
    8 B' x8 D  }/ E# H0 k' r$ h

  34. # z, D& P5 R- R- d
  35.          if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听
    ! p2 u( r) _* Z/ i# J/ W2 m  F
  36.          {
    3 w' Q* A4 Z" U
  37.                  perror("listen");" S5 L$ ?5 U) [- Z! k/ p
  38.                  return -1;3 H5 U0 Y- H0 s
  39.          }# t1 U. i5 T+ _
  40.          printf("listen............5 E+ L/ _/ I/ ]4 c
  41. ");+ q+ A3 R9 u# @  b
  42.          
    # ]" k5 @1 p! _& d; a2 Y6 r$ i8 z0 c
  43.         int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求
    1 u, |0 a8 ^7 {' f9 i: l
  44.         if (connfd < 0)+ ?6 M0 l3 t" t/ @) o1 Y8 _0 r6 \- k
  45.         {
    ; o, Y1 \  Z4 ?% x$ F  f- c
  46.                 perror("accept");
    8 a  l5 Z& r. w6 N! `6 D, u" n# C* X
  47.                 return -1;; [0 g: A) e& E- z
  48.         }
    ! k6 o! }4 k+ ]" V: v3 P
  49.         printf("accept..............
    & e. U, f9 c, B. X
  50. ");* |3 D, Q7 z  [& }5 c
  51.         char buf[100];//定义一个数组用来存储接收到的数据% z/ C4 B+ ]" j2 U
  52.         int ret;1 O4 p% I* M/ u' Y( }
  53.         while (1)5 r$ m1 F5 N, p9 H
  54.         {  h7 I1 ^% I# E# M. I$ v4 [3 E
  55.                 memset(buf, 0, sizeof(buf));0 h/ E1 n( v# ], H# }4 Q
  56.                 ret = read(connfd, buf, sizeof(buf));
    / Q$ K' V( _% W8 b; k" R4 R9 G* H& D  N
  57.                 if (0 > ret)) M3 Y" A# D( D9 E
  58.                 {9 B/ ~9 I6 D, H' j0 x! @
  59.                         perror("read");
    3 @5 S  ~, a: h- y# @5 S
  60.                         break;+ R/ u- y2 T: B! q# f
  61.                 }//执行while循环读取数据,当
    1 {. k, i' F7 H; w# X" [! H
  62.                 else if (0 == ret)% E4 ~5 [9 S! P* q4 q. Q
  63.                 {9 L, s( X) A; ^6 P6 C  \0 k% e
  64.                         printf("write close!
    , {/ T: J, ^# P* o' x
  65. ");
    7 d2 b7 _* k% B0 V8 P7 s
  66.                         break;
    " A* `; z( H7 T) R" r* b9 E3 i
  67.                 }
    0 S' D& V1 Q" w
  68.                 printf("recv: ");
    ! ?3 m* _/ M& I! d" z
  69.                 fputs(buf, stdout);//打印接收到的数据, u( e2 |3 n2 I
  70.         }
    4 [7 Y* k  D- R. ^7 U5 b. J& ~
  71.         close(sockfd);//关闭套接字- V0 s& t/ R9 ]. i$ V0 F
  72.         close(connfd);//断开连接
      k& ~1 t% o& G3 ~" X/ X" d2 X& V
  73.         return 0;
    ; w% F8 B; @8 v, ~/ M- o
  74. }
复制代码

  x0 {+ X( i5 z
' b! c. M' R/ t- n3 V: ^
  1. /*客户端*/(具体功能和服务器一样,所以不再加注释). _5 n# @% w, a$ e4 q! F" B6 n
  2. #include <stdio.h>
    # S5 x: R6 S1 a& X
  3. #include <string.h>
    ' t( |* b4 ?/ k% A0 M
  4. #include <stdlib.h>( w+ }9 p' U" N5 R( _. g; A
  5. #include <strings.h>
    + w5 S. a6 A" }) s) U
  6. #include <sys/types.h>6 s+ F. N/ R, J* S
  7. #include <sys/socket.h>& w' G/ Q, c2 H( \/ {
  8. #include <netinet/in.h>
    : N4 c5 ~  `/ s! U' n1 a( C
  9. #include <arpa/inet.h>
    ) B+ z( G8 v9 C5 G. E  r
  10. int main()
    + r( l6 C# W4 {* `
  11. {# T& {# H+ W0 O( I) }  |
  12. int sockfd;9 |, G1 d: `( l' s* G* X% l9 C
  13.         if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))5 V2 J( G9 d, s" @
  14.         {
    * D" {$ \9 Z6 x1 d+ Q
  15.                 perror("socket");
      Z' o% k4 {( r
  16.                 return -1;
    / L7 u1 B, |+ I+ |- ~
  17.         }
    + a" N" V- l! X6 e
  18.         printf("socket...........9 @. U; d$ `2 w
  19. ");' g' \$ m9 m: Q4 C# V6 g; ]9 k
  20.         4 }9 K8 i9 e6 @5 o
  21.         struct sockaddr_in srv_addr;7 Z! T- e; q6 T
  22.         memset(&srv_addr, 0, sizeof(srv_addr));
    * C# m6 }1 F7 y
  23.         srv_addr.sin_family                 = AF_INET;
    : h2 W" q5 L3 u" `! M" x
  24.         srv_addr.sin_port                         = htons(8888);4 G2 Q: W/ P% {: V1 }
  25.         srv_addr.sin_addr.s_addr         = inet_addr("192.168.3.169");# |% ~, G6 l3 `$ ?& N
  26.         if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
    % K* W% v6 }# [; ~2 O3 P' A0 y. k
  27.         {
    & _  H7 h9 _) m) k0 W( \
  28.                 perror("connect");
    3 D$ Y+ L* ~1 @+ p) u- r
  29.                 return -1; //exit //pthread_exit
    / ]( a# k& d/ L! V9 I# V
  30.         }
    ' ^- f2 q8 ]4 A5 y# z1 f3 y
  31.         printf("connect..............
    # z+ q( R- O0 ?3 r1 F9 {. M
  32. ");
    4 ^) Y5 p) W& h. {# Y
  33.         char buf[100];6 L$ j2 ^2 w% f' a$ Y
  34.         int ret;
    : U4 X3 w) [; c+ x5 T
  35.         while (1), n; i+ b- Q/ C) F5 d2 ^' s
  36.         {; P  {  O& G, V3 _" d7 C
  37.                 printf("send: ");1 r9 r- Q! x  V/ `9 D( i
  38.                 fgets(buf, sizeof(buf), stdin);5 Q6 ]) m) F: v
  39.                 ret = write(sockfd, buf, sizeof(buf));! x3 @8 S! A* y( @3 e- Y6 x* T
  40.                 if (ret < 0), ]  M% {4 a' w7 J3 ]$ Y. i
  41.                 {
    + I& x4 ~+ N, v( v. [2 h
  42.                         perror("write");/ h& e7 ?7 J4 X  Z7 _
  43.                         break;" ?( l9 c$ z- U! A" X9 \% a/ x$ f
  44.                 }" j# t( {6 |' F
  45.                 if (strncmp(buf, "quit", 4) == 0)9 a$ \# w4 v' a- b- O# o: }# D
  46.                         break;6 p+ u# ~) c1 j
  47.         }3 l, m( Q2 ?3 W
  48.         close(sockfd);
    ( i- p- u; q% D( [% v
  49.         return 0;& j# U# o; K6 b! S+ G& {1 o
  50. }
复制代码
- U) @9 e" L! d1 r
/ ], B1 Q5 y! c2 {' ~' b5 p
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

GMT+8, 2023-9-30 07:58 , Processed in 0.141406 second(s), 23 queries .

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