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

QQ登录

只需一步,快速开始

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 17732|回复: 0
打印 上一主题 下一主题

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

[复制链接]
跳转到指定楼层
楼主
发表于 2020-5-9 02:09:24 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西——socket(套接字)。
+ w$ J4 |% t+ j8 X  F' Y% d' n5 ~# `" C/ Q

" c' E2 f) x/ ?# ^8 esocket(套接字):简单来讲,socket就是用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
  i( D6 B+ ~. A+ [+ I4 f4 V' }. y+ R( L  b$ ?% S

8 g; e5 y  Z9 W# S8 f$ E) tTCP协议$ ]" ~4 j) d" ]0 \4 x# X
TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。
1 f% L' @7 q; G/ J0 g' ^
* Z+ @( j' t- e) v" X; M! [1 A3 p

7 W: ^6 V6 f2 O% x关键词:三次握手,可靠,基于字节流。5 M; c5 w- ~! b
% W# S  P( j0 a+ {3 C0 j6 k6 P

' ]$ R' v# g; B8 H" A可能有朋友会问,TCP就这么简单一句话吗?当然不是,TCP作为非常重要的传输协议,细节知识是很多的,细讲起来这一篇文章怕是不够。不过在本篇内容中,我们只需了解他的几个关键词特性,就能很好的理解下面的内容。/ Y* Q/ u  F) k4 g* X- n1 j4 X

  I. r# u2 i: _0 o6 |* KTCP服务器端和客户端的运行流程
( D/ C1 t3 V/ ~5 h- Z5 Q
1 K( o' b3 N( [( t" @

/ v+ s9 f" u6 b9 p" o' b: \. u如图,这是一个完整的TCP服务器——客户端的运行流程图,其实我个人认为程序啊,不管哪个语言都是一样,核心就在于算法的设计和函数的调用。那么图中的函数都是什么意思呢?: `/ L% L! B* T9 Z
; P& q% Z$ Q2 q% |$ P& c# H
1 X( V5 g% G$ Z1 \
1.创建socket( j# Y' i( ^# x! |8 K
socket是一个结构体,被创建在内核中
! c7 J. A" v/ E7 V sockfd=socket(AF_INET,SOCK_STREAM,0); //AF_INT:ipv4, SOCK_STREAM:tcp协议! ^4 v9 u% s3 w+ h' O/ K

2 S) v1 |/ R4 W( g
6 i/ ~$ k1 m" Q+ C, I3 z
2.调用bind函数. C; _4 i  k. s3 U2 J+ j
将socket和地址(包括ip、port)绑定。
) u" d: {+ O* ]( W# o8 i9 H2 E 需要定义一个结构体地址,以便于将port的主机字节序转化成网络字节序
; \0 ~+ a9 N" C8 V: ~' V struct sockaddr_in myaddr; //地址结构体# P- H. C8 l) u& w$ m3 X& J# _
bind函数& W! b2 w4 ^8 s; |
bind(sockfd,(struct sockaddr*)&myaddr,sizeof(serveraddr))7 t* a1 x7 H9 C0 C) }3 W5 @' P& U

  w. ]# E; T1 {- M! ^

5 S6 ~, M  P5 S: H+ N7 \+ v3.listen监听,将接收到的客户端连接放入队列" o& J& J4 N9 z% }* m
listen(sockfd,8) //第二个参数是队列长度# Q5 m; X- r* L
4 g4 x  T; j5 C9 R/ L& |

3 V# S  f) |( T, [. Z' A4.调用accept函数,从队列获取请求,返回socket描 述符& u# v6 T$ q! N3 Z  ]5 T8 v) x+ Z# T
  如果无请求,将会阻塞,直到获得连接
" @% F2 f& P, L  int fd=accept(sockfd, NULL,NULL);//这边采用默认参数6 K6 }( q' Y' A# k- E1 w

2 u, P5 j3 s0 R6 P3 l6 f
$ B5 o2 x# P/ h1 i8 N& X7 l2 n
5.调用read/write进行双向通信
8 A9 ?# ?" a4 ?9 ~$ o4 z6 c
3 j) g( O+ T: i+ B! E! w
* n% U! D2 p( a% h9 y  o0 i8 j
6.关闭accept返回的socket5 Z! J1 W8 }3 r" q4 O& [
  close(scokfd);
6 M$ i  x, b; o- i
2 \& w9 a7 E: s0 J
1 C1 [8 F) g4 Z9 Q* a3 s5 w, ?
  h! G% e4 K" V4 }
% z; C( ^, D, C1 Z
下面放出完整代码
9 N6 H: l( Q) u  D' c  p* d" D# W$ ^  N6 r
  1. /*服务器*/& m+ H# l% p6 e3 y* k
  2. #include <stdio.h>
    $ ?$ F3 B; M: a; `1 ?1 Y
  3. #include <string.h>" e" i9 n8 D/ q6 Y, Z
  4. #include <stdlib.h>
    + K% K- m* F2 B% x% g
  5. #include <strings.h>
    % T/ X! s$ N" p& l5 w# Z# b  f
  6. #include <sys/types.h>, c' D5 |7 n8 \9 g5 p
  7. #include <sys/socket.h>) l" D& e' [8 f- }% i9 H, A. G
  8. #include <arpa/inet.h>
    " c. s: r( t0 P; o0 v& {$ p# R- `4 n
  9. #include <netinet/in.h>
    * y+ B" K& Q  E5 f# b9 m% Y7 w0 \; p
  10. int main()3 F3 k4 x1 \2 j
  11. {
    8 J' c5 f6 {! A3 [$ B
  12.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字  `; }9 V0 v0 L/ {/ ]
  13.         if (sockfd < 0)7 O  z/ z' k  X7 I
  14.         {
    7 j+ X" o, D/ ^& H2 x2 U! H
  15.                 perror("socket");6 E, R2 B- m, u0 |% i
  16.                 return -1;
      z  `0 Y: W- g6 ^2 H- F$ T3 j
  17.         } //创建失败的错误处理9 D( ~% Y6 S6 `0 b# {/ q" B; {. s' G8 o
  18.          printf("socket..............
    ; c# |1 {5 P5 H' P
  19. "); //成功则打印“socket。。。。”6 r/ B- T& ?0 ]) N/ ]: O& ?
  20.          " C. C  k9 s' Z; I. K, x+ c; L. [
  21.          struct sockaddr_in myaddr; //创建“我的地址”结构体, ?0 r8 v, K" Y8 _* X
  22.          memset(&myaddr, 0, sizeof(myaddr)); //对内存清零(保险起见)! C) ^* y( B2 |  h$ `$ @% V
  23.          myaddr.sin_family                 = AF_INET; //选择IPV4地址类型$ _0 W. }) G- X! k
  24.          myaddr.sin_port                 = htons(8888); //选择端口号3 U! F+ Y: R: J7 Y' U% [; N) n2 J
  25.          myaddr.sin_addr.s_addr         = inet_addr("192.168.3.169"); //选择IP地址
    0 j  r4 b  v- N# a4 U  G9 U* T
  26. " r* V1 J) p, a' Q1 l2 `
  27. if (0 > bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))//绑定套接字4 J+ z8 ?3 K, j2 J& V
  28.          {
    - d4 y. Z1 B  v2 Z' s
  29.                  perror("bind");2 D9 _0 T5 V, _/ @, p4 [" d) s* L
  30.                  return -1;/ @0 ~5 Q4 U) U
  31.          }$ s: q/ @8 t; |! A% B1 [5 f
  32.          printf("bind..........
    # A2 x2 \0 }/ Z7 o8 S
  33. ");0 g: v" _9 B7 R  z" K& m1 u( Q

  34. 8 O" R* O9 R7 K/ J( ]' U0 p
  35.          if (0 > listen(sockfd, 8))//调用listen对指定端口进行监听3 w: {/ ?* Q  @: s! i) \! G
  36.          {
    , G$ ~( O- E0 K1 C  t* O9 v+ P
  37.                  perror("listen");
    ) X/ y* ^; d7 U# E1 v+ Z
  38.                  return -1;* D  S. Y7 Z3 c- u1 ?0 a& a
  39.          }
    , \9 ?  K$ D% b. W
  40.          printf("listen............. D3 S* z6 j* c/ S
  41. ");
    2 c  _. x, v% I3 r# O7 ^/ K
  42.          
    - n! `  _5 ^( w8 z/ \0 g! {4 q
  43.         int connfd = accept(sockfd, NULL, NULL);//使用accept从消息队列中获取请求2 P" {# `: b$ w3 \3 U
  44.         if (connfd < 0)
    ' s0 d! _* ~# x
  45.         {* s! w7 Q- \9 Y% ]3 G0 I
  46.                 perror("accept");
    3 r7 R7 C4 }0 F( G5 V  Q/ z" k
  47.                 return -1;% |2 H$ I' Q; C% E" g5 M+ l: @
  48.         }0 F) j/ e6 s+ e+ y4 i% E
  49.         printf("accept..............  i. _" u4 q" @/ O
  50. ");
    3 L# m; k/ [* K* a9 K) H
  51.         char buf[100];//定义一个数组用来存储接收到的数据+ U% |- G; P: m7 r+ Y
  52.         int ret;
    $ P3 a$ s1 |: X# Y& A
  53.         while (1)
    5 v2 O& b9 [+ l0 F
  54.         {5 j! A# x8 S* y# ^! Z
  55.                 memset(buf, 0, sizeof(buf));
    . K/ Q* d" E3 s( R% _' n6 e
  56.                 ret = read(connfd, buf, sizeof(buf));
    / S0 D5 _5 `8 ~/ d2 Y9 L; s3 V
  57.                 if (0 > ret)/ p3 l) h- m3 v: i, A; Q2 x+ ]
  58.                 {
    % y: o' c# I; P1 @
  59.                         perror("read");
    . s3 H' X$ o2 f% T
  60.                         break;
      q  H; g, ]: B+ y9 t
  61.                 }//执行while循环读取数据,当
    8 ]. _+ M7 G* ?/ F2 z
  62.                 else if (0 == ret)
    3 L  }  n9 I. R  a2 k
  63.                 {+ p/ s% U7 f3 D. b1 ]$ d8 D
  64.                         printf("write close!: Q( B7 y, O% i  T" E
  65. ");
    1 G& `# ~6 o$ a* j0 U
  66.                         break;8 c. ~0 v- `8 c6 l1 Z
  67.                 }- e, ~7 U$ ^$ k" P$ X1 m% W
  68.                 printf("recv: ");9 J- N* \3 J7 F8 P/ \/ {  v3 a& P
  69.                 fputs(buf, stdout);//打印接收到的数据' y/ l" I1 i( o
  70.         }
    % M& R/ R4 [0 Q' F% g! V2 N: S
  71.         close(sockfd);//关闭套接字
    2 ^' L( |$ I; I. e
  72.         close(connfd);//断开连接
    % J: p: b0 X) F+ t5 y1 k, n( f
  73.         return 0;
      X" I$ U5 ?( ~3 o3 `
  74. }
复制代码
3 I( I: D: X4 f7 w/ F" B7 a$ b2 S

8 j  w. g  K9 @8 D  Q9 O
  1. /*客户端*/(具体功能和服务器一样,所以不再加注释)7 z0 O$ R+ A+ U9 N) |- K& d
  2. #include <stdio.h>9 Y- a) v* h' u- Z
  3. #include <string.h>
    $ M* b- o! d2 ~: U: \; B1 r
  4. #include <stdlib.h>
    ' j; Y% W: d+ p5 c) J3 M
  5. #include <strings.h>: Q( L8 x( t$ T  P8 r
  6. #include <sys/types.h>4 e4 t+ c% M5 `. N; F6 a- d
  7. #include <sys/socket.h>
    4 M" j" p6 R, r' ~; ^7 ]7 @0 c
  8. #include <netinet/in.h>
    7 ~5 O. C% T! k2 q8 X' K1 D- k
  9. #include <arpa/inet.h>7 U: p& d0 |! Y( x
  10. int main()
    0 @- t. z7 J" k8 L( D
  11. {/ y$ R  S- W" n9 \+ k% u; a" V
  12. int sockfd;% }8 C5 K& s' a1 S& c
  13.         if (0 > (sockfd = socket(AF_INET, SOCK_STREAM, 0)))2 J- H- E2 t( D4 \8 T, G
  14.         {& P* N& T5 g' ^
  15.                 perror("socket");
    : A3 V( {  r: H( y- W
  16.                 return -1;
    7 k" H) [$ h" w. J: U8 N8 m
  17.         }: `8 ]' N0 z! {. v( Z" b. D2 @3 Z1 _
  18.         printf("socket...........  C& Y# t3 s4 p: m, |4 [# g
  19. ");
    , d! ~4 s9 L7 ?+ s0 v, W7 ?9 @  B# B
  20.           g; C$ U, h. A7 Y
  21.         struct sockaddr_in srv_addr;
    / [% `9 m: O  A' x
  22.         memset(&srv_addr, 0, sizeof(srv_addr));
    1 @% M: i; [6 O( H) {$ _$ {
  23.         srv_addr.sin_family                 = AF_INET;
    ' @, _' j+ ^2 f- c. i) h
  24.         srv_addr.sin_port                         = htons(8888);
    $ Q0 N6 }/ {0 A
  25.         srv_addr.sin_addr.s_addr         = inet_addr("192.168.3.169");
    5 @/ z7 y. J1 l/ z- a
  26.         if (0 > connect(sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))+ N% J% n! g8 N+ p% [. i, k. d
  27.         {
    % \, C3 X5 p) Y; `8 o0 U
  28.                 perror("connect");
    ) u9 G6 y. p, T, q/ e4 Y) O
  29.                 return -1; //exit //pthread_exit
    ' ~9 a) f1 g* G6 D5 N
  30.         }7 c% w! z$ e- n% n. |
  31.         printf("connect..............1 [- c- F1 M1 Z) y, f# u" {
  32. ");: e" F, [0 C' A2 S
  33.         char buf[100];
    5 V' ~' \  B! Z0 [+ V4 D
  34.         int ret;! s0 p2 ~8 _6 A& m: ]
  35.         while (1)
    & P+ w) q' D) ]# q0 G8 I! }
  36.         {
    ( ?1 N1 c9 e5 H- g3 c
  37.                 printf("send: ");
    % f+ L9 p; B/ |
  38.                 fgets(buf, sizeof(buf), stdin);+ J( |. ^. y+ y% D! H7 d/ S- f0 M8 x
  39.                 ret = write(sockfd, buf, sizeof(buf));& c4 H7 c. ~* y7 b  d
  40.                 if (ret < 0)1 M: p8 a/ w* j! K
  41.                 {( v% v2 r: K+ t# G/ i* a7 o: f$ H$ s/ W
  42.                         perror("write");
    4 ^2 ]. m; r; `: `1 ]
  43.                         break;
    / Q3 T. y. L, C( F( q
  44.                 }' W0 Y( @$ D) O3 \& K3 y7 k5 i. S
  45.                 if (strncmp(buf, "quit", 4) == 0)8 V/ m# K0 F' r0 ~
  46.                         break;
    $ C& A/ s1 N" @0 m9 P
  47.         }8 {% s9 |! P+ q8 L# _: ?
  48.         close(sockfd);
    8 o2 R* j8 k4 K6 @' \' [
  49.         return 0;
      G% [# D# d& l: \
  50. }
复制代码

$ Z+ Y( d& J8 E: F# U2 V4 i9 L6 m4 z* |+ ^
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享分享 支持支持 反对反对
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

GMT+8, 2026-8-4 07:08 , Processed in 0.058735 second(s), 23 queries .

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