欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基本TCP套接字编程

程序员文章站 2022-07-14 20:34:09
...

客户服务器链接进程实例:
基本TCP套接字编程

socket 函数
#incldue<sys/socket.h>
int socket(int family,int type,int protoclol);
第一个参数是协议族:IPV,V6,UNIX.,,,等协议.对比AFXXX和PFXX(P79), PF是一个协议族,AF是地址族.原本构想一个PF对应多个AF.但是没有是实现
第二个参数是套接字类型:比如字节流套接字,数据报套接字,有序分组套接字,原始套接字
第三个参数是传输协议:TCP,UDP,STCP
注意:没有指定地址.

connect函数:
#include<sys/socket.h>

int connect(int sockfd,const struct sockaddr*servaddr,socklen_t addrlen)
客户通过这个函数建立和服务器的链接.套接字地址结构,必须包含服务的IP和端口号

connect(描述符,套接字地址结构指针,结沟长度).
客户在调用connect之前不一定使用bind,因为内核在有需要的时候,会确定IP,并且选择一个临时端口号.
connect成功出错才会返回,如果出错可能是以为,TCP三路握手错误.


#include<sys/socket.h>di

bind(int sockfd,const struct *myaddr,socklen_t addrlen)
bind(套接字描述符,特定于协议的地址结构指针,结构长度)
对于TCP.bind可以制定一个IP或者一个端口,也可以两个都不指定.
1.如果不指定端口,在调用connect或者listen的时候,就要指定一个临时端口.但是这对于服务器来讲是无法理解的,因为服务器的端口应为大家都知道的.
2.进程可以把一个IP捆绑到套接字(特定IP),对于客户来讲,就指定了源IP地址,对于服务器来讲,就只接受这个IP的报文.如果不指定特定IP,客户就把连接时的路径当做这个特定IP,服务器就把客户SYN目的地址作为这个限定IP.
3.如果指定地址为通配地址,那么将由内核选择IP,如果端口为0,将由内核选择端口.
内核选择端口,是在bind调用的时候选择内核地址,
通配地址:对于IPV4来讲,通配地址由inaddr_any指定,例如:
struct sockaddr_in servaddr;
servadd.sin_addr.s_addr = htonl[INADDR_ANY];
但是对于V6(htonl只能32),
struct sockaddr_in6 ser;
serv,sin6_addr = in6addr_any ;
addr_any的主机字节序和网络字节序都是0,使用htonl非必须,但是规范需要.

服务器绑定非通配IP的例子(服务器只给某些IP客户服务):服务器把这些IP地址所在的组织(通常在一个子网上)的IP定义成单个网络接口的别名.


listen函数:
#include<sys/socket.h>
int listen(int sockfd,int backlog);成功返回0,出错返回-1,第二个参数是最大连接数.(已连接进程和未链接进程的总数),TCP连接过程中但是没有链接成功的是未链接
当socket函数创建一个套接字的时候,这个套接字是主动套接字,需要使用listen函数来转为一个被动套接字,这时,TCP状态转换为listeing,

accept函数:
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr*cliaddr,socklen_t *len);
int accept(监听套接字描述符,客户套接字,客户套接字长度);第二个和第三个参数是值结果参数.这个函数返回值是客户套接字的描述符.

服务器程序实例:
#include "unp.h"
#include <time.h>
#include<bits/stdc++.h>
#include<sys/socket.h>
using namespace std;
int main(int argc,char **argv){
    int listenfd,connfd;
    socklen_t len;
    struct sockaddr_in servaddr,cliaddr;
    char buff[MAXLINE];
    time_t ticks;

    listenfd = socket(AF_INET,SOCK_STREAM,0);//协议族,套接字类型,传输协议

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//不指定IP.那么内核就会选择通信地址
    servaddr.sin_port=htons(13);

    bind(listenfd,(SA*)&servaddr,sizeof(servaddr));//把一个本地协议地址赋值给一个套接字

    listen(listenfd,LISTENQ);//将主动套接字转换为被动套接字
    
    for(;;){
        len=sizeof(cliaddr);
        connfd = accept(listenfd,(SA*)&cliaddr,&len);
        printf("connect from %s,port %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,buff,(int)ntohs(cliaddr.sin_port)));
        ticks = time(NULL);
        snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));//将缓冲区中的内容拷贝到ticks,
        write(connfd,buff,strlen(buff));
        close(connfd);
    }
    return 0;
}

这是一个迭代服务器,也就是只能处理一个客户.



fork函数:
fork函数执行一次,返回两次,在父进程中返回进程ID,在子进程中返回0.
fork有两个典型用法:
1:一个进程创建一个自己的副本,这样每个副本分离操作,各不干扰.
2:一个进程要想执行另一个程序,先创建一个副本,然后其中一个副本调用exec函数,

并发服务器:
为什么父进程close()不能终止子进程连接?
	因为每个文件或者套接字都有一个进程计数.

getsockname(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);(--结果参数)
返回和某个套接字相关的本地协议地址,在没有调用bind的TCP客户上,使用getsockname()返回本地IP地址和端口号.在通配IP的服务器上,getsockname可以用于返回内核赋予该链接的本地IP,并且必须是已链接套接字的描述符,不能是监听套接字的描述付.

getpeername(int sockfd,struct sockaddr *localaddr,socklen_t *addrlen);(--结果参数)
当一个服务器是调用过accept的某个进程通过exec执行程序的时候,获取客户身份的唯一途径就是getpeername().

#include "unp.h"
int sockfd_fo_family(int sockfd){
	struct  sockaddr_storage ss;
	socklen_t len;
	len =sizeof(ss);
	if(getsockname(socfd,(SA*)&ss,&len)<){
		return -1;
	}
	return ss.ss_family;
}