网络编程 pthread / fork

本文详细介绍了TCP协议和套接字的概念,包括它们在网络通信中的作用。通过一个C语言示例展示了TCP客户端如何连接服务器并发送数据。此外,还探讨了socket函数、bind函数、listen和connect函数在TCP连接过程中的角色。最后,提到了将服务器改为多线程模式以处理多个客户端连接的方法。
摘要由CSDN通过智能技术生成

介绍

TCP:

TCP协议全称: 传输控制协议, 顾名思义, 就是要对数据的传输进行一定的控制,旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。

socket:

TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口。套接字用(IP地址:端口号)表示,区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。
套接字又可分为三种类型:字节流套接字,数据报套接字,原始套接字。
 

1. 查看while源代码

#include <stdlib.h> 
#include <stdio.h> 
#include <errno.h> 
#include <string.h> 
#include <netdb.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <sys/socket.h> 
#include <unistd.h>
#include <arpa/inet.h> 

#define portnumber 3333

int main(int argc, char* argv[]) {
	int local_socket;
	char buffer[1024];
	struct sockaddr_in server_addr;
	struct hostent* host;

	if (argc != 2) {
		fprintf(stderr, "Usage:%s hostname \a\n", argv[0]);
		exit(1);
	}

	/* 使用hostname查询host 名字 */
	if ((host = gethostbyname(argv[1])) == NULL) {
		fprintf(stderr, "ERR gethostbyname\n");
		exit(1);
	}

	/* 客户程序开始建立 local_socket描述符 */
	if ((local_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // AF_INET:Internet;SOCK_STREAM:TCP
		fprintf(stderr, "ERR socket:%s\a\n", strerror(errno));
		exit(1);
	}

	/* 客户程序填充服务端的资料 */
	bzero(&server_addr, sizeof(server_addr)); // 初始化,置0
	server_addr.sin_family = AF_INET;          // IPV4
	server_addr.sin_port = htons(portnumber);  // (将本机器上的short数据转化为网络上的short数据)端口号
	server_addr.sin_addr = *((struct in_addr*)host->h_addr); // IP地址

	/* 客户程序发起连接请求 */
	if (connect(local_socket, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1) {
		fprintf(stderr, "ERR connect:%s\a\n", strerror(errno));
		exit(1);
	}

	/* 连接成功了 */
	printf("Please typein a string:\n");

	/* 读取和发送数据 */
	fgets(buffer, 1024, stdin);
	write(local_socket, buffer, strlen(buffer));
	
	/* 结束通讯 */
	close(local_socket);
	exit(0);
}

1.1 socket函数 

int socket(int domain, int type, int protocol)

  socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

1. domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
2. type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
3. protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

1.2 bind()函数

int bind(int sockfd, struct sockaddr *myaddr, int addrlen)
  • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • myaddr:一个struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如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;     
};

ipv6对应的是:

struct sockaddr_in6 { 
    sa_family_t     sin6_family;    
    in_port_t       sin6_port;      
    uint32_t        sin6_flowinfo;  
    struct in6_addr sin6_addr;      
    uint32_t        sin6_scope_id;  
};

    struct in6_addr { 
    unsigned char   s6_addr[16];    
};
  • addrlen:对应的是地址的长度。

1.3 服务器listen,客户端connect

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
  • listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

  • connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

1.4 accept函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。
函数原型:
 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
  • accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
  • 注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
     

2.编译并在ubuntu下运行

编译:

gcc -o server-while-tcp.out server-while-tcp.c
gcc -o client.out client.c

 运行:

./server-while-tcp.out
./client.out 192.168.43.37

 

 我们看到在客户端发送指令,可以在服务器端接收到。

3. 修改服务器为多线程模式

  • 多线程发送数据,即一个服务器对应多个客户端,最多可以同时监听10个客户端。

  • 客户端向服务器发起连接请求,成功后打印连接信息。

  • 客户端向服务器发送数据后,服务器端打印数据。

  • 服务器将得到的数据全部转换为大写后返回到客户端。

  • 客户端打印出从服务器端返回的数据。

编译运行命令如上,函数和运行效果如下:

 运行命令:

gcc -o server-pthread-tcp.out server-pthread-tcp.c
gcc -o client.out client.c

 

./server-pthread-tcp.out
./client.out 192.168.43.37

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值