TCP编程

发布于:2024-09-18 ⋅ 阅读:(141) ⋅ 点赞:(0)

目录

一丶socket

1.简介

2.类型

3.端口 port

4.端口转换

5.IP地址转换

二丶TCP编程

函数接口:

1.socket

2.bind

3.linten

4.accept

5.recv

6.connect

7.send

服务器代码:

客户端代码


一丶socket

1.简介

1》1982 - Berkeley Software Distributions 操作系统引入了socket作为本地进程之间通信的接口
2》1986 - Berkeley 扩展了socket 接口,使之支持UNIX 下的TCP/IP 通信
3》现在很多应用 (FTP, Telnet) 都依赖这一接口 

Socket
 1、是一个编程接口,API---->函数Socket();
 2、是一种特殊的文件描述符 (everything in Unix is a file)
 3、并不仅限于TCP/IP协议
 4、面向连接 (Transmission Control Protocol - TCP)
 5、无连接 (User Datagram Protocol -UDP )
 
为什么需要Socket?
  普通的I/O操作过程 
  •打开文件->读/写操作->关闭文件 
  •TCP/IP协议被集成到操作系统的内核中,引入了新型的“I/O”操作 
  •进行网络通信的两个进程在不同的机器上,如何连接?
  •网络协议具有多样性,如何进行统一的操作 
需要一种通用的网络编程接口:Socket

2.类型

流式套接字(SOCK_STREAM)   TCP

        提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。

数据报套接字(SOCK_DGRAM)  UDP
        提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

原始套接字(SOCK_RAW)
        可以对较低层次协议如IP、ICMP直接访问。

3.端口 port

● 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区分● TCP端口号与UDP端口号独立
● 端口用两个字节来表示 2byte 16bit

众所周知端口:1~10231~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151 (选1000以上10000以下)
动态或私有端口:49152~65535

套接字创建以后,套接字对应的端口号可以指定,如果不指定,则随机分配

4.字节序
        发送时需要将主机字节序转换成网络字节序(大端)
        1.  小端字节序:低序字节存储在低地址上;
        2.  大端字节序:低序字节存储在高地址上;
        注意:数据的读取顺序都是从低地址往高地址读取,通过大小端转换得到结果。

4.端口转换

主机字节序转换为网络字节序 (小端序->大端序)

u_long htonl (u_long hostlong); //host to internet long

u_short htons (u_short short);  //掌握这个
网络字节序转换为主机字节序(大端序->小端序)
u_long ntohl (u_long hostlong);

u_short ntohs (u_short short);

5.IP地址转换

typedef uint32_t in_addr_t;
struct in_addr {
    in_addr_t s_addr;
};

in_addr_t inet_addr(const char *cp);  //从人看的到机器识别的32位无符
号整数
char * inet_ntoa(struct in_addr in);  //从机器到人

二丶TCP编程

(C/S架构:client server B/S架构:browser server)

服务器-------------------------------------------------------------------》接电话者
        1.  创建套接字(socket)---------------》有手机
        2.  指定网络信息---------------------------》有号码
        3.  绑定套接字(bind)------------------》绑定手机(插卡)
        4.  监听套接字(listen)-----------------》待机
        5.  接收客户端连接连接请求(accept)--》接电话
        6.  接收、发送数据(recv send)---》通话
        7.  关闭套接字(close)-----------------》挂电话

客户端-------------------------------------------------------------------》打电话者
        1.  创建套接字(socket)------------》有手机
        2.  指定(服务器)网络信息--------》有对方的号码
        3.  连接(connect)-------------------》拨打电话
        4.  接收发送消息(recv send)---》通话
        5.  关闭套接字(close)------------》挂电话

函数接口:

1.socket

int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
   domain:协议族
     AF_UNIX, AF_LOCAL  本地通信
     AF_INET            ipv4
     AF_INET6            ipv6
  type:套接字类型
     SOCK_STREAM:流式套接字
     SOCK_DGRAM:数据报套接字
      SOCK_RAW:原始套接字
  protocol:协议 - 填0 自动匹配底层 ,根据type
  系统默认自动帮助匹配对应协议
     传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
     网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
 返回值:
    成功 文件描述符
    失败 -1,更新errno

2.bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定
参数:
    socket:套接字
    addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信当时socket第一个参数确定)   
    addrlen:结构体大小   
  返回值:成功 0   失败-1,更新errno
  
 通用结构体:
  struct sockaddr {
     sa_family_t sa_family;
     char        sa_data[14];
 }

ipv4通信结构体:
struct sockaddr_in {
    sa_family_t    sin_family;
    in_port_t      sin_port;  
    struct in_addr sin_addr;  
};
struct in_addr {
    uint32_t       s_addr;    
};

本地通信结构体:
 struct sockaddr_un {
     sa_family_t sun_family;               /* AF_UNIX */
     char        sun_path[108];            /* pathname */
 };

3.linten

int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
 sockfd:套接字
 backlog:同时响应客户端请求链接的最大个数,不能写0.
  不同平台可同时链接的数不同,一般写6-8个
    (队列1:保存正在连接)
    (队列2,连接上的客户端)
   返回值:成功 0   失败-1,更新errno 

4.accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
   Sockfd :套接字
   addr: 链接客户端的ip和端口号
      如果不需要关心具体是哪一个客户端,那么可以填NULL;
   addrlen:结构体的大小
     如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值: 
     成功:文件描述符; //用于通信
		失败:-1,更新errno

5.recv

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据 
参数: 
    sockfd: acceptfd ;
    buf  存放位置
    len  大小
    flags  一般填0,相当于read()函数
    MSG_DONTWAIT  非阻塞
返回值: 
   < 0  失败出错  更新errno
   ==0  表示客户端退出
   >0   成功接收的字节个数

6.connect

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
     sockfd:socket函数的返回值
     addr:填充的结构体是服务器端的;
     addrlen:结构体的大小
返回值 
      -1 失败,更新errno
      正确 0 

7.send

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
    sockfd:socket函数的返回值
    buf:发送内容存放的地址
    len:发送内存的长度
    flags:如果填0,相当于write();

服务器代码:

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int ret,acceptfd;
    // 1.创建套接字(socket)---------------》有手机
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd); // 3
    // 2.指定网络信息---------------------------》有号码
    struct sockaddr_in saddr,caddr;
    saddr.sin_family = AF_INET;                         // IPV4
    saddr.sin_port = htons(atoi(argv[1]));                       // 端口号
    //saddr.sin_addr.s_addr = inet_addr("192.168.50.13"); // 虚拟机IP
    //saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    saddr.sin_addr.s_addr = INADDR_ANY;
    int len=sizeof(caddr);
    // 3.绑定套接字(bind)------------------》绑定手机(插卡)
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind ok\n");
    // 4.监听套接字(listen)-----------------》待机
    if (listen(sockfd, 6) < 0)
    {
        perror("listen err");
        return -1;
    }
    printf("listen ok\n");
    // 5.接收客户端连接连接请求(accept)--》接电话
    // tcp服务器一共有两类文件描述符,一类用于连接,一类用于通信
    // socket函数返回值:用于连接的文件描述符
    // accept函数返回值:用于通信的文件描述符
    while (1)
    {

        acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("accept err");
            return -1;
        }
        printf("port:%d ip:%s\n",ntohs(caddr.sin_port),inet_ntoa(caddr.sin_addr));
        printf("acceptfd:%d\n", acceptfd);

        // 6.接收、发送数据(recv send)---》通话
        while (1)
        {
            // read/write()
            ret = recv(acceptfd, buf, sizeof(buf), 0);
            if (ret < 0)
            {
                perror("recv err");
                return -1;
            }
            else if (ret == 0)
            {
                printf("client exit\n");
                break;
            }
            else
            {
                printf("buf:%s\n", buf);
                memset(buf, 0, sizeof(buf));
            }
        }
        // 7.关闭套接字(close)-----------------》挂电话
        close(acceptfd);
    }
    close(sockfd);

    return 0;
}

客户端代码

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    // 1.创建套接字(socket)------------》有手机
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    // 2.指定(服务器)网络信息--------》有对方的号码
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    // 3.连接(connect)-------------------》拨打电话
    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("connect err");
        return -1;
    }
    printf("connect okk\n");
    // 4.接收发送消息(recv send)---》通话
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        if (!strcmp(buf, "quit"))
            break;
        // write(sockfd, buf, sizeof(buf));
        send(sockfd, buf, sizeof(buf), 0);
    }
    // 5.关闭套接字(close)------------》挂电话
    close(sockfd);
    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到